Send SMS using nexmo gem from input field in rails - ruby-on-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.

Related

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.

Mail attachment from form file field and send mail through sidekiq

In rails, I want to send email using Action Mailer with attachment that is obtained from form file field and want to delay it through sidekiq.
And I have written code as below.
In view:
<%= form_tag({ controller: 'my_controller', action: 'my_mail', method: 'post' }, { multipart: true }) do %>
<%= form_field_tag(:attachment) %>
<% end %>
In controller:
def my_mail
MyMailer.delay.my_mail(params)
end
In Mailer:
def my_mail(message)
attachments['attachment'] = File.read(message[:attachment].tempfile)
mail(from: ENV['MY_MAIL'], to: ENV['MAIL_RECIVER'], subject: 'this is subject')
end
But, IOError will be raised due to inaccessibility to the file.
And, I perform File read operation in controller as
def my_mail
MyMailer.delay.my_mail(File.read(params[:attachment].tempfile))
end
Now, I can make attachment in Mailer as
attachments['attachment'] = message
And Now, It does work as i want but It's very bad to read file in controller due to security reason.
So, Now I want to know the best way to attach the file obtained from form and send it through sidekiq.
In controller:
Its not good to send bulk objects like params and File object in redis-server via sidekiq. Lets make it simple
def my_mail
# get absolute path of temporary location uploaded file
attachment_tmp_path = File.absolute_path(params[:attachment].tempfile)
MyMailer.delay.my_mail(attachment_tmp_path)
end
In Mailer:
def my_mail(attachment_tmp_path)
attachments['attachment'] = File.read(attachment_tmp_path)
mail(from: ENV['MY_MAIL'], to: ENV['MAIL_RECIVER'], subject: 'this is subject')
end
Why security issue is warned?
It is not considered good to directly use params without using strong params to limit only permitted attributes.
Note:
Tmp uploaded files may not be available always as you have used sidekiq for background processing file uploaded by sidekiq-client may not be available when sidekiq-server (Background version) tries to access that tmp file when sidekiq-server being busy processed this task after very long long time.
Conditions your approach may not work:
When you goto to production and need to run multiple instance. Lets
say you need a separate utility instance to run sidekiq and
redis. Then your sidekiq-server cannot locate the tmp location
of application_master.
When you sidekiq-server is busy in
processing or is down for some time and resumes after long long time

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

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.

button to save current page in rails 3.2

I need to have a button to save the current web site (just like clicking on "Save as"), I created a method in the controller which works great for any external site (like http://www.google.com) but doesn't work for the sites inside my application, I get a timeout error!. This has no explanation to me :(
Any clue what is the issue?
#CONTROLLER FILE
def save_current_page
# => Using MECHANIZE
agent = Mechanize.new
page = agent.get request.referer
send_data(page.content, :filename => "filename.txt")
end
I tried also Open URI, same problem!
#CONTROLLER FILE
def save_current_page
# => USANDO OPEN URI
send_data(open(request.referer).read, :filename => "filename.txt")
end
I'm using rails 3.2 and ruby 1.9, any help is appreciated, I already spent like 10 hours trying to make it work!!
Rails can only handle one request at a time. It's a never-ending standoff between the two requests - the first request is waiting for the second request, but the second request is waiting for the first request, and therefore you get a Timeout error. Even if you're running multiple instances of the app with Passenger or something, it's a bad idea.
The only way I can think to get around it would be to use conditional statements like so:
referer = URI.parse(request.referer)
if Rails.application.config.default_url_options[:host] == referer.host
content = "via yoursite.com"
else
agent = Mechanize.new
page = agent.get request.referer
content = page.content
end
send_data content, filename: "filename.txt"
A little dirty but it should get around the Timeout problem. As far a getting the actual content of a page from your own site - that's up to you. You could either render the template, grab something from cache, or just ignore it.
A much better solution would be to enqueue this code into something like Resque or Delayed Job. Then the queue could make the request and wait in line to request the page like normal. It would also mean that the user wouldn't have to wait while your application make a remote request, which is dangerous because who knows how long the page will take to respond.
After several hours and lots of other posts I got to a final solution:
Bricker is right in that it is not possible for rails to render more than once in a call, as taken from http://guides.rubyonrails.org/layouts_and_rendering.html "Can only render or redirect once per action"
The site also states "The rule is that if you do not explicitly render something at the end of a controller action, Rails will automatically look for the action_name.html.erb template in the controller’s view path and render it."
Then, the solution that worked great for me was to tell the controller to render to a string if a download flag (download=true) was set in :params (I also use request.url to have it working from any view in my application)
View:
= link_to 'Download', request.url+"&downloadexcel=true", :class => 'btn btn-primary btn-block'
Controller:
def acontrolleraction
#some controller code here
if params[:downloadexcel]
save_page_xls
else
# render normally
end
end
def save_page_xls
#TRESCLOUD - we create a proper name for the file
path = URI(request.referer).path.gsub(/[^0-9a-z]/i, '')
query = URI(request.referer).query.gsub(/[^0-9a-z]/i, '')
filename = #project_data['NOMBRE']+"_"+path+"_"+query+".xls"
#TRESCLOUD - we render the page into a variable and process it
page = render_to_string
#TRESCLOUD - we send the file for download!
send_data(page, :filename => filename, :type => "application/xls")
end
Thanks for your tips!

Showing error messages in a redirected request

I am using Authlogic to do some simple signup and login stuff. In my WelcomeController I want to have the signup and login forms on the same page 'index' with form actions set to their individual controllers, UsersController and UserSessionsController, respectively. These controllers redirect the user to the protected profile page within the site on successful signup/login. On error I need to redirect back to the WelcomeController#index and display their errors. Upon doing a redirect this information is lost and I cannot use a render because it is a different controller. What is the correct way to handle this behavior?
I could perhaps store the error messages in the Flash Hash. This seems like the easiest solution.
Lately I have stumbled across this problem in another application I was writing where I needed to render summary pages from researcher submitted RFP forms from a PeerReviewerController. Seemed like in that case the use of now deprecated components would have been the right way to handle this. ie: render_component :controller => 'RFPForms', :action => 'summary', :id => 213
Components seem like the DRY way to do something like this. Now that we don't have them what is the correct solution?
One simple and easy way to do this is to pass a parameter on the redirect:
redirect_to welcome_url(:login_error=>true)
In your view or controller you can then test for that param:
<% if params[:error] -%>
<div class="error">My Error Message</div>
<% end -%>
You could do a render:
render :template => "welcome/index"
But you'd have to make sure that any variables that page expects are loaded, or it won't render.
Drew's answer does not cater for more complex error data (e.g. an array of validation errors), and Marten's answer would risk violating the DRY rule by needing to duplicate code from WelcomeController#index.
A better-looking answer (which is an elaboration on the original poster's idea of storing error data in the flash) is available in Rails validation over redirect although unfortunately I am still personally struggling to get it working ...

Resources