ActionCable - Respond With Error - ruby-on-rails

With ActionCable, how can I respond with an error after receiving data from a client?
For example, when the client fails to authenticate, ActionCable throws UnauthorizedError which responds with a 404. I want to respond with a 422, for example, when the data that the client sent is invalid.

ActionCable.server.broadcast "your_channel", message: {data: data, code: 422}
Then in your.coffee file:
received: (res) ->
switch res.code
when 200
# your success code
# use res.data to access your data message
when 422
# your error handler

From what I could gather there's no "Rails way" to do this, the answer given by #Viktor seems correct. In summary: ensure all messages are broadcasted with both data and code, then switch by code in the client.
For a more modern and ES6 example see below:
In rails:
require 'json/add/exception'
def do_work
// Do work or raise
CampaignChannel.broadcast_to(#campaign, data: #campaign.as_json, code: 200)
rescue StandardError => e
CampaignChannel.broadcast_to(#campaign, data: e.to_json, code: 422) # Actually transmitting the entire stacktrace is a bad idea!
end
In ES6:
campaignChannel = this.cable.subscriptions.create({ channel: "CampaignChannel", id: campaignId }, {
received: (response) => {
const { code, data } = response
switch (code) {
case 200:
console.log(data)
break
default:
console.error(data)
}
}
})

Related

How to implement Paypal payouts with paypal smart button integration in rails

I have implemented PayPal checkout API in my rails application by using the SmartButtons and by creating the order in the server-side.
I have used the payouts-ruby-sdk gem and my code is as follows:-
index.html.erb
<!-- Set up a container element for the button -->
<div id="paypal-button-container"></div>
<!-- Include the PayPal JavaScript SDK -->
<script src="https://www.paypal.com/sdk/js?client-id=xyz&currency=USD"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Call your server to set up the transaction
createOrder: function(data, actions) {
return fetch('/orders', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
return orderData.orderID;
});
},
// Call your server to finalize the transaction
onApprove: function(data, actions) {
return fetch('/orders/' + data.orderID + '/capture', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show a success / thank you message
// Your server defines the structure of 'orderData', which may differ
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
// Recoverable state, see: "Handle Funding Failures"
// https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
return actions.restart();
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
if (errorDetail.description) msg += '\n\n' + errorDetail.description;
if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
// Show a failure message
return alert(msg);
}
// Show a success message to the buyer
alert('Transaction completed');
});
}
}).render('#paypal-button-container');
</script>
orders_controller.rb
class OrdersController < ApplicationController
skip_before_action :verify_authenticity_token
def index
end
def create
# Creating Access Token for Sandbox
client_id = 'xyz'
client_secret = 'abc'
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
request = PayPalCheckoutSdk::Orders::OrdersCreateRequest::new
request.request_body({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: "10.00"
}
}
]
})
begin
# Call API with your client and get a response for your call
# debugger
response = client.execute(request)
puts response.result.id
render json: {success: true, orderID: response.result.id}
rescue PayPalHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
end
def execute_payment
client_id = 'xyz'
client_secret = 'abc'
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
request = PayPalCheckoutSdk::Orders::OrdersCaptureRequest::new(session[:orderID])
begin
# Call API with your client and get a response for your call
response = client.execute(request)
# If call returns body in response, you can get the deserialized version from the result attribute of the response
order = response.result
puts order
rescue PayPalHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
end
end
Now I want to implement the Paypal's Payouts API and I know that paypal-ruby-sdk is available for it but I am confused where to fit this code and how to integrate it with the front end. Any ideas? Thanks in advance :)
Your code above is Checkout code, for both front-end (JavaScript), and back-end (Ruby).
Payouts has nothing to do with Checkout, neither front-end Checkout nor back-end Checkout.
Payouts is strictly a backend API operation, where you send money from your account to another account.
Payouts does not connect to any front-end UI. You can build your own UI to trigger a payout, if you need one. Presumably you know who you want to send money from your account to, and what process should trigger this action.

Problem when displaying errors in friendly way in Zapier

I'm trying to display errors in a friendly way, but I'm always getting the errors stack trace with console logs that I want to get rid of.
The idea is to create a Lead in our platform using any source, for example, Google Sheets.
When an invalid email is provided in the lead and posted to our API, I'm getting the expected message I want to display followed by the stack trace.
My custom error message is
INVALID FORMAT for email. Object didn't pass validation for format email: as1#mail.
But this is what I'm getting:
INVALID FORMAT for email. Object didn't pass validation for format email: as1#mail. What happened: Starting POST request to https://cosmo-charon-production.herokuapp.com/v1/lead/vehicle Received 500 code from https://cosmo-charon-production.herokuapp.com/v1/lead/vehicle?api-key=gIBp04HVdTgsHShJj6bXKwjbcxXTogsh after 62ms Received content "{"code":"SCHEMA_VALIDATION_FAILED","message":"Request validation failed: Parameter (lead) failed sch" INVALID FORMAT for email. Object didn't pass validation for format email: as1#mail. Console logs:
Image showing error displayed in Zapier
I've added a middleware for ErrorHandling into afterResponse, just as one of the examples provided in Zapier docs.
The function analyzeAndParse() receives an error object from the API and returns a string with the error message translated in a friendly way
const checkForErrors = (response, z) => {
// If we get a bad status code, throw an error, using the ErrorTranslator
if (response.status >= 300) {
throw new Error(analyzeAndParse(response.json))
}
// If no errors just return original response
return response
}
This is the code that creates a Lead in our platform, making a request to our API.
function createLead (z, bundle) {
const industry = bundle.inputData.industry
// add product to request based on the inputFields
leadType[industry].addProductFields(bundle.inputData)
const requestOptions = {
url: `${baseUrl}lead/${_.kebabCase(industry)}`,
method: 'POST',
body: JSON.stringify(checkSourceForCreate(bundle.inputData)),
headers: {
'content-type': 'application/json'
}
}
return z.request(requestOptions).then((response) => {
if (response.status >= 300) {
throw new Error(analyzeAndParse(response.content))
}
const content = JSON.parse(response.content)
if (content && content.leads) {
// get only the last lead from the list of leads
content.lead = content.leads[0]
delete content.leads
}
return content
})
}
Any ideas?
Thanks!

How to check response's status code in zapier's test cases

I am writing some code to test action in Zapier's CLI. I want to add one more condition here something like response.status == 200 or 201; to check API response code is 200 or 201.
How can I do it? when I log response it gives me whole JSON object that
API is returning.
describe("contact create", () => {
it("should create a contact", done => {
const bundle = {
inputData: {
firstName: "Test",
lastName: "Contact",
email: "Contact#test.com",
mobileNumber: "+12125551234",
type: "contact"
}
};
appTester(App.creates.contact.operation.perform, bundle)
.then(response => {
// Need one more condition whether response status is 200 or 201.
response.should.not.be.an.Array();
response.should.have.property('id');
done();
})
.catch(done);
});
});
appTester returns the result of the perform method, which isn't an API response. It's the data that's passed back into Zapier.
The best thing to do is to add a line like this to your perform:
// after your `z.request`
if (!(response.status === 200 || response.status === 201)) {
throw new Error('need a 200/201 response')
}
That will ensure you're getting exactly the response you want. But, more likely, you can add a response.throwForStatus() to make sure it's not an error code and not worry if it's exactly 200/201.

axios async await function not returning validation errors

export const register = (user, callback, errorback) => async dispatch => {
try{
let response = await axios.post(`${PINGUIN_ROOT_URL}/users/create`, user)
if (response.data.auth_token){
auth_token = response.data.auth_token
dispatch({ type: REGISTER_SUCCESS, payload: auth_token})
callback()
} else {
let error = response
throw error
}
}catch(error){
dispatch({type: REGISTER_FAIL})
errorback()
}
Hi, I am building a login register based off of what we have learned. It works but for some reason the error validations wont come back. I built a rails api and I see the validation errors when I use postman but when I try to get the errors back using redux the register function above gets to the "let response = .." line and immediately goes to the catch(error) line. I dont know how to pass back the actual validation errors that I see when I use post man because the error that is being caught is the following:
Error: Request failed with status code 422
at createError (createError.js:16)
at settle (settle.js:18)
at XMLHttpRequest.handleLoad (xhr.js:77)
at XMLHttpRequest.dispatchEvent (event-target.js:172)
at XMLHttpRequest.setReadyState (XMLHttpRequest.js:538)
at XMLHttpRequest.__didCompleteResponse (XMLHttpRequest.js:381)
at XMLHttpRequest.js:485
at RCTDeviceEventEmitter.emit (EventEmitter.js:181)
at MessageQueue.__callFunction (MessageQueue.js:250)
at MessageQueue.js:101
Now again, the code is working when it actually logs in the user however it fails to actually give me the validation errors that I need. I see the validation errors comming back as json in postman but i do not get to see them in practice. Help please?
You can get the response object from your error object as error.response
try{
let response = await axios.post(`${PINGUIN_ROOT_URL}/users/create`, user)
...
} catch(error){
console.error(error.response)
}

asynchronous HTTP calls using EventMachine::Iterator

I am trying to make concurrent asynchronous HTTP calls using EventMachine::Iterator. I see too many calls ended up in errcallback. Is there any way to retry them? I see all these have deferred_status failed. How to avoid getting into that failed state.
urls = ["http://www.google.com"]*1000 #using this as an example
EventMachine.run do
EM::Iterator.new(urls, 1000).map(proc { |url, iter|
res = EventMachine::HttpRequest.new(url).get
sleep(0.100)
res.callback {
puts "#{res.req.uri} #{res.response_header.status}"
response =(JSON.parse(res.response)['response']) if res.response_header.status == 200
iter.return(response)
}
res.errback{
puts "Err => #{res.req.uri} #{res.response_header.status}"
iter.return(res.response_header.status)}
}, proc { |responses|
all = responses.flatten
puts 'all done!'
EM.stop
})
end

Resources