Rails controller - execute action only if the a Rails UJS method inside succeed (mutually dependent methods) - ruby-on-rails

Following another question (Rails controller - execute action only if the two methods inside succeed (mutually dependent methods)), I would like to ensure that inside one of my controller's action, if the user does not see the message displayed by a Rails UJS method, then the first methods of the controller action are not implemented either.
CONTEXT
I have a Controller with a method called 'actions'. When the app goes inside the method 'example_action', it implements a first method (1) update_user_table and then (2) another update_userdeal_table. (both will read and write database) and then (3) a third method which is related to a Rails UJS(ajax) call.
My issue is the following: in case of timeout in the middle of the controller, I want to avoid the case where the User table (via method 1) is updated, the UserDeal table is updated (via method 2) but NOT the thrid method i.e the ajax request that displays a message FAILS (error, timeout,...status like 500 or 404 or canceled or timeout...).
In my app, for mobile users if they're in a subway with internet connection, they launch the request that goes through 'example_action' controller, performs successfully the first method (1) and second method (2) but then they enter a tunnel for 60 seconds with very very low (<5b/sec) or NO internet connection, so for UX reasons, I timeout the request and display to the user 'sorry it took too long, try again'. The problem is that if I could not show to them the result (3), I need to be able to not execute (1) and(2).
I need the two methods (1) and(2) and(3) to be "mutually dependent": if one does not succeed, the other one should not be performed. It's the best way I can describe it.
Today Here is my code. It's not working as I am manually testing by clicking and then after just 2 seconds I disconnect the internet connection. I see in my database that (1) and(2) were performed and the databases were updated but I saw the message 'sorry it took too long, try again'.
Is that the right approach ? if yes how to do this?
If not, should I try a different angle like: if (1) and(2) were successful but not(3) should I store the fact the rails UJS xhr status was an error or timeout, that consequently the modal wxas not effectively displayed to the user and then show to them the result/message once they get back online?
Here is the code
html page for the user
the user click on a button that triggers a Rails UJS aajax request that will display ultimately the modal message
<div id=zone">
<%= link_to image_tag(smallest_src_request),
deal_modal_path,
remote: true %>
</div>
This send to a route that points to this controller action
Deal controller
class DealsController < ApplicationController
def deal_modal
Deal.transaction do
update_user_table # that's the (1)
update_userdeal_table # that's the (2)
# show_modal_message
respond_to do |format|
format.js
end
end
private
def update_user_table
# update the table User so it needs to connect to internet and acces the distant User table
end
def update_userdeal_table
# update the table UserDeal table so it needs to connect to internet and access the distant UserDeal table
end
end
This points to a js.erb view file
deal_modal.js.erb
showModalMessage("Here is your result <variable and all>);
To manage the ajax, error, timeouts... (if necessary to the resolution of the question), I use Rails UJS settings.
IMPORTANT: It is here that in case of error or timeout, I send the error / timeout modal message that comes in place of the one you normally get (see just above "Here is your result..")
$(document).on('page:change', function () {
$("#zone").
on('ajax:error',function(event,xhr, status, error){
console.log(' ajax call failed:', error);
var msg;
msg = Messenger().post({
hideAfter: 4,
message: "sorry it took too long, try again."
});
});
$(document).on('page:change', function () {
//set timeout Rails UJS ajax option that will display message for ajax:error cases defined above
$.rails.ajax = function(options) {
if (!options.timeout) {
options.timeout = 5000;
}
return $.ajax(options);
};
});

So the transaction will only rollback if an error is thrown. If an unhandled error is thrown, your application will crash and it will show a 500 error in some way.
In order to display the response to the user, on success or error, you will need to render something. So you don't want to prevent the respond_to block from executing. One way to handle this would be to set a flag via an instance variable.
def deal_modal
begin
Deal.transaction do
update_user_table
update_userdeal_table
end
#success = true
rescue
#success = false
end
# show_modal_message
respond_to do |format|
format.js
end
end
Then in deal_modal.js.erb
<% if #success %>
showModalMessage("Here is your result <variable and all>");
<% else %>
showModalMessage("There was a problem");
<% end %>
EDIT:
Dealing with connection issues is definitely tricky and there isn't really one ideal solution. I would generally let the database continue uninterrupted and let it return either a success or failure on it's own time. For lengthy transactions, you can use a gem like delayed_job or sidekiq to process the action in the background and let the rails controller return a response saying "...pending..." or something. Unless you're using websockets on the frontend, this means continually polling the server with ajax requests to see if the background process is complete.

Related

500 error for stripe webhook event checkout.session.completed

I am currently working on stripe webhooks for my rails application and am encountering a problem. All events except for checkout.session.completed are working.
My main goal is to change the payment status booking.paid to true when the event checkout.session.completed happens. The stripe webhooks logs give me a 500 internal server error for the event checkout.session.completed. I think the problem is in my Webhook controller but I just can't figure out what's wrong. Any help would be amazing!
This is my Webhooks_controller:
class WebhooksController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
def create
payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
event = nil
begin
event = Stripe::Webhook.construct_event(
payload, sig_header, Rails.application.credentials[:stripe][:webhook]
)
rescue JSON::ParserError => e
status 400
return
rescue Stripe::SignatureVerificationError => e
# Invalid signature
puts "Signature error"
p e
return
end
# Handle the event
case event.type
when 'checkout.session.completed'
# session = event.data.object
# #booking.session.client_reference_id.paid = true
booking = Booking.find_by(checkout_session_id: event.data.object.id)
booking.update(paid: true)
end
render json: { message: 'success' }
end
end
I just happen to be writing the exact same feature as you so I'm glad this popped up in my queue.
From taking a quick look at the code nothing stands out much. If we know that the only event that doesn't work is checkout.session.completed, and that's the only one we're even processing, that narrows the problem down a bit... so here's what I did:
I copied your implementation into a controller in my Rails API project, then used the Stripe CLI to listen for, and forward Stripe events to the new endpoint:
$ stripe listen --forward-to http://localhost:3000/webhook_events
I commented out the actual handling of the event so it was only processing the event.
I then used the Stripe CLI in a new terminal to trigger a checkout.session.completed event:
$ stripe trigger checkout.session.completed
Once I did this, my API responded with a 201 and Stripe was happy.
So after all of that, as the previous answer suggests, I think the issue lies with your updating the Booking model, so I have a few suggestions to make working with webhooks in general easier:
Ideally, your controller should respond with a 2xx to Stripe as soon as you've verified the authenticity of the event with the Stripe gem.
Once you've completed that, I would immediately move the processing of the event to a background job using ActiveJob.
In the background job, you know that your event is valid and that the session completed successfully, so now you can start to update your Booking model. The arguments to the job could be as simple as just the Stripe checkout session ID.
Finally, splitting the responsibilities like this will make writing tests much easier (and will catch what the actual problem is!).
I hope this helps, good luck!
I think the issue might lie in the Booking.find_by method. Try adding a line to inspect the value of booking prior to updating its status.
when 'checkout.session.completed'
under this use print(session)
it will show error in the console that is affecting or showing 500 error in checkout session

Action Cable Broadcast message fron sidekiq shows up only after refresh, works instantly from console

I followed this tutorial to create an action cable broadcast but it's not quite working as expected. The channel streams and the web app subscribes successfully, but messages broadcasted from the sidekiq background job are only displayed after refreshing the page. Using the same command on the console does result in an immediate update to the page.
When looking at the frames in chrome's developer mode, I cannot see the broadcasted messages from the background job but can immediately see the ones sent by the console. However, I can confirm that the sidekiq background job is broadcasting those messages somewhere since they do show up upon refresh; however, I don't know where they are being queued.
Are there any additional configuration changes needed to keep the messages from the background job from being queued somewhere? Are there any typos or errors in my code that could be causing this?
Action Cable Broadcast message:
ActionCable.server.broadcast "worker_channel", {html:
"<div class='alert alert-success alert-block text-center'>
Market data retrieval complete.
</div>"
}
smart_worker.rb: -- This is called as perform_async from the controller's action
class SmartWorker
include Sidekiq::Worker
include ApplicationHelper
sidekiq_options retry: false
def perform
ActionCable.server.broadcast "worker_channel", {html:
"<div class='alert alert-success alert-block text-center'>
Market data retrieval complete.
</div>"
}
end
connection.rb:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = current_user #find_verified_user ignored until method implemented correctly and does not always return unauthorized
end
private
def find_verified_user
if current_user = User.find_by(id: cookies.signed[:user_id])
current_user
else
reject_unauthorized_connection
end
end
end
end
worker_channel:
class WorkerChannel < ApplicationCable::Channel
def subscribed
stream_from "worker_channel"
end
def unsubscribed
end
end
worker.js:
App.notifications = App.cable.subscriptions.create('WorkerChannel', {
connected: function() {
console.log('message connected');
},
disconnected: function() {},
received: function(data) {
console.log('message recieved');
$('#notifications').html(data.html);
}
});
cable.yml
development:
adapter: redis
url: redis://localhost:6379/1
test:
adapter: async
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: smarthost_production
Also added
to the view but that didn't make a difference.
I'm not sure this is the entire explanation but this is what I have observed through further testing:
After multiple server restarts, the broadcast started working and would log as expected in the development logger. Console messages where still hit or miss, so I added some additional identifiers to the broadcasted messages and identified that they were being broadcasted before the loading of the next page was completed. This caused two things:
1) A quick flashing of flash messages triggered by the broadcast (in what was perceived to be the old page - i.e. only works after a refresh)
2) A lack of or inconsistent behavior in the browser console: Because the sidekiq worker job finished so quick, sometimes even before the browser started rendering the new page, I believe the console messages are being reset by the page loading actions and are therefore not visible when you check the logs (or even if you stare at it for a while).
It seems as though this is working as expected, and is simply working to quickly in the local environment which makes it seem as though it's not working as intended.
ActionChannel normally does not queue messages and those broadcasted when there's no subscriber should be lost. Observed behaviour can happen if notification actually comes later than you expect.
I'd check:
Run entire job in console, not just notification, and see if it's running slow
Check sidekiq queues latency
Add logging before/after notification in job and check logs if the job is actually run successfully

Redirect and then render

Okay, so real quick, I am using a file upload plugin http://plugins.krajee.com/file-input to upload my images. The plugin expects some sort of response from the server, and i want to send back an empty json object.
But when the images are uploaded, I also need to redirect immediately to another place so people can sort of make changes to the order.
Rails says I can't use render and redirect, but says i can redirect and return.
How do i redirect and return the empty json object??
def create
if !params[:images].nil?
package = Package.first
#photos = Array.new
#order = current_user.orders.new
#order.save
#order.order_items.each{|d| d.delete} #Stupid hack to prevent creation of fake order items. Don't know what is causing this yet
params["images"].each do |i|
photo = current_user.photos.create
photo.write(i.original_filename, i.read)
photo.save
#order.order_items.create(photo_id: photo.id, size_id: package.size_id, material_id: package.material_id)
end
redirect_to edit_order_path(#order) and return
else
flash[:danger] = "Please select at least one photo to upload"
redirect_to upload_photos_path
end
end
If the upload plugin you're using is expecting a JSON response and you would like to redirect after a successful upload, then you'll need to do it client side.
If you're not using Rails 4 or Turbolinks, you can simply redirect via window.location.replace. From your Rails code it looks like you're batch uploading in which case you'll want to assign a callback to the filebatchuploadsuccess event as per the docs
Example:
$('#fileinputid').on('filebatchuploadsuccess', function(event, data, previewId, index) {
// files have been successfully uploaded, redirect
window.location.replace( '/your_path_here' );
});
If you are using Turbolinks, the above code will be exactly the same except that instead of window.location.replace, you can use Turbolinks.visit
Example:
$('#fileinputid').on('filebatchuploadsuccess', function(event, data, previewId, index) {
// files have been successfully uploaded, redirect
Turbolinks.visit( '/your_path_here' );
});

Why does my Net::HTTP.post_form timeout?

In my rails app controller I am posting to the api of the app on the same machine. I have build this out to handle the posting the data to the url:
url = "http://172.16.155.165:3000/api/jobs"
params = {
:input => "original/video.h264",
:output => "new/video.mp4",
:preset => 'h264'
}
jobResults = Net::HTTP.post_form(URI.parse(url), params)
This works great when I run this code through rails console but when I use it in my controller it gives me this error after loading for a minute or so:
Timeout::Error in SeminarsController#create
Timeout::Error
Once the timeout happens the data is actually posted and the api does what it should. It is like it is hanging until it times out then posts the data. The controller never goes beyond this step though. It should write the response body to a file with jobResults.body which would work fine if it didn't time out. If I write this into rails console it outputs the response immediately. The api will never take a whole minute to respond.
Am I doing something to cause this to happen? How can I make it work right?
edit:
This is the code for create in app/controllers/api/jobs_controller.rb:
def create
job = Job.from_api(params, :callback_url => lambda { |job| api_job_url(job) })
if job.valid?
response.headers["X-State-Changes-Location"] = api_state_changes_url(job)
response.headers["X-Notifications-Location"] = api_notifications_url(job)
respond_with job, :location => api_job_url(job) do |format|
format.html { redirect_to jobs_path }
end
else
respond_with job do |format|
format.html { #job = job; render "/jobs/new"}
end
end
end
Yes. Ideally you should remove the long running process (yes this is long running process) into background job. Remember that when many users start updating the videos, this process will show down for many reasons (like bandwidth, API acceptance rate etc). Rake::Timeout always pops out if the process passes the threshold. It is actually designed to abort requests that are taking too long to respond. And, it is not raised in console.
How can I make it work right?
Move it to the background job. Or you can explictly increase the rake timeout interval by doing something like this
# config/initializers/timeout.rb
Rack::Timeout.timeout = 30 # seconds
But i suggest not to do this. This rake-timeout helps in debugging. Mainly people use in heroku with newrelic.

Activity stream testing

I'm building a activity stream feature in my app, all activities are created via controller methods.
When the controller method is successful the activity is recorder to activity table. Since my controller action is invoked via ajax, I tried posting directly from my rspec test.
I tried this :
Net::HTTP.post_form(URI.parse("http://localhost:5000#{inquiry_path}"), { :id=> #user.id, :type => 'Inquiry' })
This is what I see gets posted in my firebug, when making ajax call:
id:55
type:Inquiry
url:/user_inquiry
So I'm asserting whether the activity count has changed :
it "activity count should be changed" do
PublicActivity::Activity.count.should == 1
end
After I'm done with testing that count has changed, I wanted to see the user home page actually displaying the new notification.
But this test fails :
Failure/Error: PublicActivity::Activity.count.should == 1
expected: 1
got: 0 (using ==)
Just in case I put the puts statement in my controller, to check if the call actually reaches the controller :
puts "Hereeee"
I don't see the message getting printed so I guess the request never reaches the controller, why is that?
Am I doing something wrong here, how can I test this?
My test idea was ok except the part where the request didn't arrive to the controller, here is how I solved it (before and it):
page.driver.post("#{inquiry_path}?id=" + #user.id)
it "post request should return the right response code" do
page.driver.status_code.should eql 200
end
Now my requests are arriving to the controller.

Resources