Rails is attempting to render remotely... but not - ruby-on-rails

I'm working on a rails application and am attempting to convert the event_calendar gem's "next month" link into an ajax response.
I set the link to remote:
def month_link(month_date)
link_to I18n.localize(month_date, :format => "%B"),
{:month => month_date.month, :year => month_date.year},
remote: true
end
told it to respond to js...
respond_to do |format|
format.html
format.js { render text: "help me!" }
end
And it works!
Started GET "/calendar/2012/6" for 127.0.0.1 at 2012-07-03 15:27:42 -0500
Processing by CalendarController#index as JS
Parameters: {"year"=>"2012", "month"=>"6"}
Event Load (0.3ms) SELECT "events".* FROM "events" WHERE (('2012-05-27 05:00:00.000000' <= end_at) AND (start_at< '2012-07-01 05:00:00.000000')) ORDER BY start_at ASC
Rendered text template (0.0ms)
Completed 200 OK in 14ms (Views: 0.7ms | ActiveRecord: 0.3ms)
well... except for the part where it doesn't actually render anything I pass it. If I just tell it to format.js w/o the render, it doesn't actually respond to a js file.
What could cause a render to not display?
Updates
I just noticed that if you access the url like so localhost:3000/calendar/2012/6.js It works as expected, So I would assume it's an issue with how the link is set up?
Ok, I got the js file working, but I have no clue why. I think I was miss-using render (although I could have sworn I had used it for debugging purposes once). I guess render only actually render an html page when responding to an html request. Would make sense since it passes json to javascript for ajax requests.
Another part of the issue was I was trying to use CoffeeScript with either index.js.coffee.erb or index.js.erb.coffee. I thought it was working for the longest time, but what was really happening, was it was using the original index.js.erb I had written first, even though I had already deleted it. Once I restarted the server, everything broke.

Try this:
def month_link(month_date)
link_to I18n.localize(month_date, :format => "%B"),
{:remote=>true, :month => month_date.month, :year => month_date.year}
end
The format of link_to you are wanting to use is:
link_to(body, url_options = {}, html_options = {})
The :remote=>true wants to be in the url_options. I'm not sure what the :month & :year keys are for, but if they are html options, you would want this:
def month_link(month_date)
link_to I18n.localize(month_date, :format => "%B"),
{:remote=>true},
{:month => month_date.month, :year => month_date.year}
end

It seems that by default the remote option ignores any attempts to render or redirect. Considering that the point of Ajax is to prevent both of these... I can see why.
For self reference here is what (to my knowledge) happens when you create a remote link_to:
line 51 of jquery_ujs.js
$.rails = rails = {
// Link elements bound by jquery-ujs
linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
following linkClickSelector we find this function at line 300
$(document).delegate(rails.linkClickSelector, 'click.rails', function(e) {
var link = $(this), method = link.data('method'), data = link.data('params');
if (!rails.allowAction(link)) return rails.stopEverything(e);
if (link.is(rails.linkDisableSelector)) rails.disableElement(link);
if (link.data('remote') !== undefined) {
if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }
if (rails.handleRemote(link) === false) { rails.enableElement(link); }
return false;
} else if (link.data('method')) {
rails.handleMethod(link);
return false;
}
});
Assuming that handleRemote handles the AJAX we wind up at line 107 to find this monster
// Submits "remote" forms and links with ajax
handleRemote: function(element) {
var method, url, data, crossDomain, dataType, options;
if (rails.fire(element, 'ajax:before')) {
crossDomain = element.data('cross-domain') || null;
dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
if (element.is('form')) {
method = element.attr('method');
url = element.attr('action');
data = element.serializeArray();
// memoized value from clicked submit button
var button = element.data('ujs:submit-button');
if (button) {
data.push(button);
element.data('ujs:submit-button', null);
}
} else if (element.is(rails.inputChangeSelector)) {
method = element.data('method');
url = element.data('url');
data = element.serialize();
if (element.data('params')) data = data + "&" + element.data('params');
} else {
method = element.data('method');
url = rails.href(element);
data = element.data('params') || null;
}
options = {
type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain,
// stopping the "ajax:beforeSend" event will cancel the ajax request
beforeSend: function(xhr, settings) {
if (settings.dataType === undefined) {
xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
}
return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
},
success: function(data, status, xhr) {
element.trigger('ajax:success', [data, status, xhr]);
},
complete: function(xhr, status) {
element.trigger('ajax:complete', [xhr, status]);
},
error: function(xhr, status, error) {
element.trigger('ajax:error', [xhr, status, error]);
}
};
// Only pass url to `ajax` options if not blank
if (url) { options.url = url; }
return rails.ajax(options);
} else {
return false;
}
},

Related

Server side Rails model validation in a React form

I am trying to validate User inputs on server side in a Rails Application with React as view. Basically I make axios calls to the Rails API like this:
const Script = props => {
const [script, setScript] = useState({})
const [scene, setScene] = useState({})
const [loaded, setLoaded] = useState(false)
useEffect(() => {
const scriptID = props.match.params.id
const url = `/api/v1/scripts/${scriptID}`
axios.get(url)
.then( resp => {
setScript(resp.data)
setLoaded(true)
})
.catch(resp => console.log(resp))
}, [])
const handleChange = (e) => {
e.preventDefault()
setScene(Object.assign({}, scene, {[e.target.name]: e.target.value}))
}
const handleSubmit = (e) => {
e.preventDefault()
const csrfToken = document.querySelector('[name=csrf-token]').content
axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken
const script_id = script.data.id
axios.post('/api/v1/scenes', {scene, script_id})
.then(resp => {
const included = [...script.included, resp.data.data]
setScript({...script, included})
})
.catch(err => {
console.log(err.response.data.error)
})
.finally(() => {
setScene({name: '', description: ''})
})
}
All data gets passed into a react component with a form.
return (
<div className="wrapper">
{
loaded &&
<Fragment>
.
.
.
<SceneForm
handleChange={handleChange}
handleSubmit={handleSubmit}
attributes={script.data.attributes}
scene={scene}
/>
</Fragment>
}
</div>
)
In this form I have a name field and the corresponding name in Rails has a validation uniqueness: true. everything works fine if I enter a valid (unique) name.
I tried to implement a validation but I am not happy with the outcome. (It works in general: my no_errors? method does what is is supposed to do and I get a 403 status) This is the controller part:
def create
scene = script.scenes.new(scene_params)
if no_error?(scene)
if scene.save
render json: SceneSerializer.new(scene).serialized_json
else
render json: { error: scene.errors.messages }, status: 422
# render json: { error: scene.errors.messages[:name] }, status: 423
end
else
render json: { error: "name must be unique" }, status: 403
end
end
.
.
.
private
def no_error?(scene)
Scene.where(name: scene.name, script_id: scene.script_id).empty?
end
If I enter an existing name I get a console.log like this:
screenshot
Here is my concern: I am not happy with my approach of error handling in general. I do not want to get the 403 message logged to the console (I do want to avoid this message in the first place).
My idea is to take the "simple form" approach: Make the border of my field red and post an error message under the field, without any console output...
And on a side note: Is 403 the correct status? I was thinking about 422 but wasn't sure...
Thank you for your ideas in advance!
403 is the wrong status code. What you need is to return a 422 (unprocessable entity). 403 is more about policy and what you are authorized to do.
Then when you deal with http request it's a standard to have a request and status code printed in browser console. Not sur to get your issue here.
If it's about to display the error you could just have a function that colorize (or whatever fireworks you want) your input if the status code response is a 422.

Tying in the PayPal smart buttons with a Ruby on Rails form_for and controller/method?

I have the smart buttons "working" in sandbox but I can't think of any way to attach the smart buttons success to the order form which creates the order. With Stripe Elements, it's pretty plug and play because it's on the page and a part of the form itself, but with PayPal with the redirects, I can't seem to think of a way.
Does this require javascript or can I do this without it, aside from what's already there?
Form:
<%= form_for(#order, url: listing_orders_path([#listing, #listing_video]), html: {id: "payment_form-4"} ) do |form| %>
<%= form.label :name, "Your Name", class: "form-label" %>
<%= form.text_field :name, class: "form-control", required: true, placeholder: "John" %>
#stripe code here (not important)
<%= form.submit %>
<div id="paypal-button-container"></div>
<!-- Include the PayPal JavaScript SDK -->
<script src="https://www.paypal.com/sdk/js?client-id=sb&currency=USD"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: <%= #listing.listing_video.price %>
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Show a success message to the buyer
alert('Transaction completed by ' + details.payer.name.given_name + '!');
});
}
}).render('#paypal-button-container');
</script>
Create Method in Controller:
require 'paypal-checkout-sdk'
client_id = Rails.application.credentials[Rails.env.to_sym].dig(:paypal, :client_id)
client_secret = Rails.application.credentials[Rails.env.to_sym].dig(:paypal, :client_secret)
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
#amount_paypal = (#listing.listing_video.price || #listing.listing_tweet.price)
request = PayPalCheckoutSdk::Orders::OrdersCreateRequest::new
request.request_body(
{
intent: 'AUTHORIZE',
purchase_units: [
{
amount: {
currency_code: 'USD',
value: "#{#amount_paypal}"
}
}
]
}
)
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
#order.paypal_authorization_token = response.id
rescue BraintreeHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers['debug_id']
end
How can I tie in the PayPal smart buttons with the form so once the payment is completed, it creates an order if successful?
UPDATE:::::::
Created a PaypalPayments controller and model:
controller:
def create
#paypal_payment = PaypalPayment.new
#listing = Listing.find_by(params[:listing_id])
require 'paypal-checkout-sdk'
client_id = "#{Rails.application.credentials[Rails.env.to_sym].dig(:paypal, :client_id)}"
client_secret = "#{Rails.application.credentials[Rails.env.to_sym].dig(:paypal, :client_secret)}"
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
#amount_paypal = #listing.listing_video.price
request = PayPalCheckoutSdk::Orders::OrdersCreateRequest::new
#paypal_payment = request.request_body({
intent: "AUTHORIZE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: "#{#amount_paypal}"
}
}
]
})
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
# #order.paypal_authorization_token = response.id
rescue BraintreeHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
# if #paypal_payment.create
# render json: {success: true}
# else
# render json: {success: false}
# end
end
Javascript in view:
paypal.Buttons({
createOrder: function() {
return fetch('/paypal_payments', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID;
});
},
onApprove: function(data) {
return fetch('/orders', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
orderID: data.orderID
})
}).then(function(res) {
return res.json();
}).then(function(details) {
alert('Authorization created for ' + details.payer_given_name);
});
},
}).render('#paypal-button-container');
With this, the paypal box appears but then goes away right after it loads with this in the CMD:
#<OpenStruct id="1Pxxxxxxx394U", links=[#<OpenStruct href="https://api.sandbox.paypal.com/v2/checkout/orders/1P0xxxxxxx394U", rel="self", method="GET">, #<OpenStruct href="https://www.sandbox.paypal.com/checkoutnow?token=1P07xxxxxxx94U", rel="approve", method="GET">, #<OpenStruct href="https://api.sandbox.paypal.com/v2/checkout/orders/1Pxxxxxxx4U", rel="update", method="PATCH">, #<OpenStruct href="https://api.sandbox.paypal.com/v2/checkout/orders/1P07xxxxxxx394U/authorize", rel="authorize", method="POST">], status="CREATED">
No template found for PaypalPaymentsController#create, rendering head :no_content
Completed 204 No Content in 2335ms (ActiveRecord: 15.8ms)
I have not used smart buttons. However, you should not have "a ton more code" in a create action. If you are following MVC and rails conventions. It would seem that you need a seperate controller action to handle the payment authorization separately from the create action. But if you can get to this point in your javascript, here is example of how you would send the data from paypal javascript back to your controller, this will need some work but hopefully it points you in the right direction:
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Show a success message to the buyer
alert('Transaction completed by ' + details.payer.name.given_name + '!');
// here is where you should send info to your controller action via ajax.
$.ajax({
type: "POST",
url: "/orders",
data: data,
success: function(data) {
alert(data); // or whatever you wanna do here
return false;
},
error: function(data) {
alert(data); // or something else
return false;
}
});
});
}
This is most likely far too late, but ill add what worked for me.
You need to return the response ID to the PayPal script as a json object. All you need to do is update your create function like so :
...
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
render json: { orderID: order.id }
# #order.paypal_authorization_token = response.id
rescue BraintreeHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
...

How to get rails ajax to recognize that a date was updated and reflect that on the page?

I have a link
%a{href: "#", :data => {verifying_link: 'yes', id: link.id, table_row: index}}
verify_by_js
that calls
$(function(){
$("a[data-verifying-link]='yes'").click(function(){
// spinner here
a=$(this).parent()
a.html('-spinner-')
var id= $(this).data("id");
var row = $(this).data("tableRow");
$.get("/verify_link/"+id+"&table_row="+row, function(data) {
if (data.link.verified_date !== undefined) {
$("span#verify_link_"+row).html('<span class="done">Verified</span>');
} else {
$("span#verify_link_"+row).html('<span class="undone">Unverified</span>');
}
});
//a.html('DONE')
});
});
It does get called, I see the -spinner- text but then I always get 'Unverified' in the ui, even though the record was verified (verified date set) in the datbase. If I actually refresh the browser page however I see the verified date which shows that the update was actually successful.
The controller code is:
def verify_link
#link = Link.find(params[:id])
if #link.valid_get?
#link.update_attribute(:verified_date, Time.now)
end
end
The network tab is showing a valid get for /verify_link/377&table_row=0
The piece I feel I am not getting is how function(data) {
if (data.link.verified_date !== undefined works (it was recommended from another question-answer and how that is used by the get call.
Server log shows:
Started GET "/verify_link/377&table_row=0" for 127.0.0.1 at 2014-08-03 21:52:06 -0400
Processing by LinksController#verify_link as */*
Parameters: {"id"=>"377&table_row=0"}
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
Link Load (0.4ms) SELECT `links`.* FROM `links` WHERE `links`.`id` = 377 LIMIT 1
(0.1ms) BEGIN
(0.4ms) UPDATE `links` SET `verified_date` = '2014-08-04 01:52:06', `updated_at` = '2014-08-04 01:52:06' WHERE `links`.`id` = 377
(0.5ms) SELECT COUNT(*) FROM `links` WHERE (1 = 1 AND position = 347)
(44.6ms) COMMIT
Rendered links/verify_link.js.erb (0.2ms)
Completed 200 OK in 347.0ms (Views: 5.3ms | ActiveRecord: 46.3ms)
which is a little confusing as it references app/view/links/verify_link.js.erb but this isn't what is used as it has
$ cat app/views/links/verify_link.js.erb
<%- if #link.verified_date %>
$("span#verify_link_<%=params['table_row']%>").html('<span class="done">Verified</span>');
<%- else %>
$("span#verify_link_<%=params['table_row']%>").html('<span class="undone">Unverified</span>');
<%- end %>
and that has the text Verified or Unverified whereas I made the previous one have different text - Verified OK and Not Verified to make sure and it is those being used in the ui and it is ("Not verified").
do this thing in your controller. As i dont see any proper return value on success and error.
def verify_link
#link = Link.find(params[:id])
if #link.valid_get?
if #link.update_attribute(:verified_date, Time.now)
render nothing: true, status: 200
else
render nothing: true , status: 422
end
end
end
Do this to the desire javascript file. And change $.get to $.ajax since $.get callback is only called when the request was successful not on failier. Indirectly it also means that, since the DOM is updating (its okay incorrectly) due to the $.get callback it means that the request is successful. I am using $.ajax as it looks more elegant.
$(function(){
$("a[data-verifying-link]='yes'").click(function(){
// spinner here
a=$(this).parent()
a.html('-spinner-')
var id= $(this).data("id");
var row = $(this).data("tableRow");
$.ajax({
url: "/verify_link/"+id+"&table_row="+row ,
type: 'GET',
success: function(r) {
$("span#verify_link_"+row).html('<span class="done">Verified</span>');
},
error: function(r) {
$("span#verify_link_"+row).html('<span class="undone">Unverified</span>');
},
complete: function(r) {
a.html('DONE');
}
});
});
});

Ruby on rails Ajax call showing 404 error

I am making and ajax call to hit the controller but it is showing the 404 error:
My controller method is like:
def get_user_time
if(params[:user])
#user_time_checks = UserTimeCheck.where(:user_id => params[:user])
end
end
And my route for this is like:
post "user_time_checks/get_user_time"
And my ajax script is like:
function get_user_time(id) {
var user_id = id;
if(user_id != ''){
$.ajax({
url:"get_user_time?user="+user_id,
type:"POST",
success: function(time){
console.log(time);
},error: function(xhr,response){
console.log("Error code is "+xhr.status+" and the error is "+response);
}
});
}
}
Try this:
$.ajax({
url:"user_time_checks/get_user_time",
type:"POST",
data: {
user: user_id
},
success: function(time){
console.log(time);
},error: function(xhr,response){
console.log("Error code is "+xhr.status+" and the error is "+response);
}
});
Also make sure you really need to do POST method and that rails route does not require specific paramater like :user_id. Basically check the output from
rake routes | grep get_user_time
Your route should be:
post "user_time_checks/get_user_time" => "user_time_checks#get_user_time"
Also, since the purpose of the request is to get some data, you should make it a GET request instead. So:
function get_user_time(id) {
var user_id = id;
if(user_id != ''){
$.get("get_user_time",
{user: user_id})
.success(function(time) {
console.log(time);
})
.error(function(xhr,response){
console.log("Error code is "+xhr.status+" and the error is "+response);
});
}
}
Lastly, maybe you should tell the controller to be able to repond_to json:
def get_user_time
if(params[:user])
#user_time_checks = UserTimeCheck.where(:user_id => params[:user])
respond_to do |format|
format.html # The .html response
format.json { render :json => #user_time_checks }
end
end
end

Check username availability

I have a form to user login:
<%= form_tag(#action, :method => "post", :name => 'signup' ,:onSubmit => 'return validate();') do %>
<%= label_tag(:user, "Username:") %>
<%= text_field_tag(:user) %>
I want to check if there is the username in the database immediately after :user-field lost focus. I can override this event on the form with javascript, but I can not send Ruby-AJAX request from javascipt code.
Is there any way to check username without adding additional controls (buttons, links) on the form?
You can use some JavaScript (this one written with jQuery) for AJAX cheking:
$(function() {
$('[data-validate]').blur(function() {
$this = $(this);
$.get($this.data('validate'), {
user: $this.val()
}).success(function() {
$this.removeClass('field_with_errors');
}).error(function() {
$this.addClass('field_with_errors');
});
});
});
This JavaScript will look for any fields with attribute data-validate. Then it assings onBlur event handler (focus lost in JavaScript world). On blur handler will send AJAX request to the URL specified in data-validate attribute and pass parameter user with input value.
Next modify your view to add attribute data-validate with validation URL:
<%= text_field_tag(:user, :'data-validate' => '/users/checkname') %>
Next add route:
resources :users do
collection do
get 'checkname'
end
end
And last step create your validation:
class UsersController < ApplicationController
def checkname
if User.where('user = ?', params[:user]).count == 0
render :nothing => true, :status => 200
else
render :nothing => true, :status => 409
end
return
end
#... other controller stuff
end
For what reason can you not send an ajax request from javascript code?
The best way would be to send a GET ajax request when the focus is lost. The get request could then return true or false and your javascript could then reflect this on the page.
I answered this in another post.
It is a friendly way for validating forms if you do not want to write it all from scratch using an existing jquery plugin. Check it out and if you like it let me know!
Check username availability using jquery and Ajax in rails
The solution that #Viacheslav has, works fine and my answer is a combination of his and my own changes (especially JS) part.
We will be using Ajax in order to achieve this.
Lets first create our function in the controller
def checkname
if !User.find_by_display_name(params[:dn])
render json: {status: 200}
else
render json: {status: 409}
end
return
end
and then adding our routes in routes.rb
resources :yourcontroller do
collection do
get 'checkname'
end
end
Now lets gets our hand on the view. Below you'll see the input:
.field
= f.text_field :display_name, onblur: "checkDisplayName.validate(this.value)"
%p.error-name.disp-none username exists
And now by help of JSwe get the magic rolling. Blow JS has few functions. validate does the actually validation. getStatus is our Ajax call to get the status and we use showError & disableSubmitButton to our form a bit more production ready to show errors and disabling the submit button.
var checkDisplayName = {
validate: function(dn){
checkDisplayName.getStatus(dn).then(function(result) {
if (!!result){
if (result.status != 200){
checkDisplayName.disableSubmitButton(true);
checkDisplayName.showError();
} else{
checkDisplayName.disableSubmitButton(false);
}
}
});
return false;
},
getStatus: async (dn) => {
const data = await fetch("/pages/checkname?dn=" + dn)
.then(response => response.json())
.then(json => {
return json;
})
.catch(e => {
return false
});
return data;
},
showError: function() {
let errEl = document.getElementsByClassName('error-name')[0];
if (!!errEl) {
errEl.classList.remove("disp-none");
window.setTimeout(function() { errEl.classList.add("disp-none"); },3500);
}
},
disableSubmitButton: function(status){
let button = document.querySelector('[type="submit"]');
button.disabled = status;
}
};

Resources