Rails coflict ajax and URI.encode - ruby-on-rails

I have a search string that, through my controller, connects to my written library, and sends a get request to a third-party api. The form:
<%= form_with(:url => url_for(:controller => 'orders', :action => 'find_drug'), method: "get") do %>
<%= text_field_tag :drug_name, nil, class: "search_drug", placeholder:"Find drug..." %>
<%= button_to "Find", nil, class: "search_drug_button" %>
<% end %>
Ajax:
$('document').ready(function() {
$('.search_drug_button').click(function(){
$.ajax({
url: 'orders/find_drug',
}).done(function(data) {
console.log(data);
});
});
});
controller OrdersController:
def find_drug
response = ApiParser::Parser.new
drugs = response.get_drug(params[:drug_name]).response
respond_to do |format|
format.json { render json: drugs.body}
end
end
Method in my library (written using the HTTParty gem):
module ApiParser
class Parser
include HTTParty
...
def get_drug(drug)
options = "?search.drugs={\"ls\": \"#{URI.encode(drug)}\"}"
self.class.get(options, format: :json)
end
...
end
end
In the console, 2 requests are sent (one of the form, directly to the controller with code 200, the second via ajax) and ajax gives me an error 500
NoMethodError in OrdersController#find_drug
undefined method `gsub' for nil:NilClass
Extracted source (around line #305):
#303 unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false)
#304 end
*305 str.gsub(unsafe) do
#306 us = $&
#307 tmp = ''
#308 us.each_byte do |uc|
Extracted source (around line #104):
#102 def escape(*arg)
#103 warn "#{caller(1)[0]}: warning: URI.escape is obsolete" if $VERBOSE
*104 DEFAULT_PARSER.escape(*arg)
#105 end
#106 alias encode escape
#107 #
Extracted source (around line #38):
**#this is my code**
*38 options = "?search.drugs={\"ls\": \"#{URI.encode(drug)}\"}"
#39 self.class.get(options, format: :json)
request in console Chrome 200:
...localhost:3000/orders/find_drug?utf8=%E2%9C%93&drug_name=%D1%8F%D1%80%&authenticity_token=Ndao2...
500:
...localhost:3000/orders/find_drug
If, in the controller, instead of params[:drug_name], I simply pass the string I want to find, both queries with code 200 work. Having written the whole question, I thought that my ajax is not receiving this particular params[:drug_name] , and can not search for an empty value(but it is not exactly). Just starting to work with javascript
I tried to perform such an Ajax request:
$('document').ready(function() {
$('.search_drug_button').click(function(e){
e.preventDefault();
$.ajax({
url: 'orders/find_drug',
data: new FormData($(this).closest("form")[0])
}).done(function(data){
console.log(data);
});
});
});
but I got an error
jquery-3.3.1.min.js:2 Uncaught TypeError: Illegal invocation
at i (jquery-3.3.1.min.js:2)
at jt (jquery-3.3.1.min.js:2)
at Function.w.param (jquery-3.3.1.min.js:2)
at Function.ajax (jquery-3.3.1.min.js:2)
at HTMLInputElement.<anonymous> (Аптечная_сеть:551)
at HTMLInputElement.dispatch (jquery-3.3.1.min.js:2)
at HTMLInputElement.y.handle (jquery-3.3.1.min.js:2)

First let me tell you why your code isn't working.
You're using form and jQuery together
If you want to use jQuery you should block all the default actions which is happening on your submit button by using event.preventDefault(); otherwise you'll be sending two requests one with default form submission and other with jQuery.
Solution:
you can use rails default functionality by using js.erb.
To get this working.
Use this in your controller code.
def find_drug
response = ApiParser::Parser.new
#drugs = response.get_drug(params[:drug_name]).response
end
comment out all your ajax code.
replace your code with form_with(url: '/orders/find_drug', method: :get)
create a file in orders folder as find_drug.js.erb
write console.log('<%= #data %>'); in find_drug.js.erb
now check it. If you're still facing problems do let me know.

Related

Rails is rendering an empty partial from js.erb

I'm trying to render a partial from a js.erb file but nothing is shown in the view.
There is an AJAX call that triggers the report_announcement action in the announcements_controller when a button is pressed.
reports.js
$.ajax({
type: "POST",
contentType: "application/json",
url: "/announcements/report_announcement.js",
data: json_data
});
reports/_report_content.html.erb
<div id="report_announcements_container"></div>
announcements_controller.rb
def report_announcement
#announcement = Announcement.new(announcement_params)
respond_to do |format|
if #announcement.save
format.js {}
end
end
end
announcements/report_announcement.js.erb
I know that announcements/report_announcement.js.erb is rendering ok because I can see the logging statement in the console and also in the development.log as well as the report_announcement object being logged to the console.
<% announcement_json = #announcement.to_json.html_safe %>
var report_announcement = <%= announcement_json %>;
console.log('report_announcement');
console.log(report_announcement);
// this is where something is not right I think
$("#report_announcements_container").html("<%= escape_javascript(render partial: 'announcements/report_announcements', locals: { announcement: #announcement }) %>");
console.log('inside announcements....');
announcements/report_announcements.html.erb
This is where I'm having an issue because the logging statement for the partial is shown however nothing from the partial is shown on the page. I see in the development.log that the page rendered but nothing is actually shown in the view. The partial is not showing on the page in the report_announcements_container div.
<h1>test</h1>
<%= announcement %>
<script>
console.log('inside partial...');
</script>
You're using the wrong content-type in your ajax call. Rails recognizes both application/javascript and text/javascript as the :js format.
Also when you request a js format you actually have to execute the returned javascript for anything to happen in the client.
$.ajax({
type: "POST",
contentType: "application/javascript",
url: "/announcements/report_announcement.js",
data: json_data
}).done(function(data){
jQuery.globalEval(data); // Yeah eval is evil.
});
If you have done any "real" ajax calls before this will seem completely backwards - thats because it is. js.erb is a poor mans ajax framework for the javascript impaired which consists of a bunch of routes and views that mutate the DOM in the client.
Manually creating calls for a js.erb template is just a silly excerise in masocism. If you really want to use it create a form and use it send the request instead so that you can just leverage the javascript handlers that rails provide:
<%= form_with(url: "/announcements/report_announcement", remote: true) do |f| %>
# ...
<% end %>

AJAX not rendering as it should

I am at a loss with what’s stopping my code not rendering AJAX where it should be, I have a less serious JS ‘Parse error’ which I can’t work out also.
I have a default prevented rails form_for where upon submit event jQuery finds the element and its attribute values, posts them to the model via appropriate action, model then responds with the new object and is supposed to render the JSON via a jbuilder form.
All is fine when I get the page to render via a redirect, but not by a render ‘create’, content_type: :json, error displayed is a missing template error. I also see from network response that it ‘failed to load response data’. views/reviews/create.json.jbuilder is saved is in the correct place I believe, class and id names are all correct I believe, files and folders are named correctly and in the right place I believe, I can’t see anything wrong? Unsure whether it’s a jbuilder error, a controller syntax error, or a jQuery syntax error. Here is my code:
controllers/reviews_controller.rb:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.new(params[:review].permit(:thoughts, :rating))
if #restaurant.reviews.find_by user_id: current_user.id
flash[:notice] = "You already reviewed this restaurant!"
redirect_to restaurants_path
else
#review.user = current_user
#review.save
# redirect_to restaurants_path, will do a redirect, but defeats AJAX purpose!
render 'create', content_type: :json # results in a missing template error #'missing templete reviews/create' error
end
end
Assets/restaurants/restaurants.js:
$(document).ready(function(){
$('.new_review').on('submit', function(event){
event.preventDefault();
var reviewList = $(this).siblings('ul');
var currentRestaurant = $(this).parent();
$.post($(this).attr('action'), $(this).serialize(), function(review){
if review # This line results in Uncaught SyntaxError: Unexpected Identifier
var newReview = Mustache.render($('#review_template').html(), review);
reviewList.append(newReview);
currentRestaurant.find('.review_count').text(review.restaurant.review_count)
currentRestaurant.find('.average_rating_number').text(review.restaurant.average_rating);
currentRestaurant.find('.average_rating_stars').text(review.restaurant.average_rating_stars);
}, 'json');
});
});
views/restaurants/index.html.erb (jbuilder template element):
<template id='review_template'>
<li>
<strong>{{ star_rating }}</strong> -*- {{ thoughts }}
</li>
</template>
views/reviews/create.json.jbuilder:
json.thoughts #review.thoughts
json.star_rating star_rating(#review.rating)
json.restaurant do
json.average_rating number_with_precision(#restaurant.average_rating,
precision: 1)
json.average_rating_stars star_rating(#restaurant.average_rating)
json.review_count pluralize(#restaurant.reviews.count, 'reviews')
end
Been on this for hours now trying to solve this one, pfff!! any idea where I’m going wrong folks? Am I doing something really dim somewhere here? Thank you.
As you can see in the error message, rails is looking for a file called reviews/create[extension] or application/create[extension] and extensions allowed are .erb, .builder, .raw, .ruby, .coffee or .jbuilder.
I suggest to change you ajax call to ask for js, not json, create a file called reviews/create.js.erb, and add this kind of code :
<% if #restaurant.reviews.find_by user_id: current_user.id %>
// do the code to show the error message in javascript
<% else %>
reviewList.append("<%= j(render('create')) %>");
currentRestaurant.find('.review_count').text(<%= pluralize(#restaurant.reviews.count, 'reviews') %>)
currentRestaurant.find('.average_rating_number').text(<%= number_with_precision(#restaurant.average_rating, precision: 1) %>);
currentRestaurant.find('.average_rating_stars').text(<%=star_rating(#restaurant.average_rating) %>);
<% end %>
This code should be executed after the success of the creation. You also have to create a file called reviews/_create.html.erb with the html you want to show. Finally, you have to delete some logic in the javascript and in the controller.

Rails with jQuery + AJAX fundamentals

I've been given a task to do a simple Task Manager on Ruby On Rails. It's pretty simple for me but there is one issue. Everything has to be "ajaxified" and data should be passed around in JSON.
How I do this right now.
On index.html.erb
I'm creating a simple form with these parameters:
<%= form_for(Task.new, remote: true, :html => {:'data-type' => 'json'}) do |f| %>
In TasksController:
class TasksController < ApplicationController
respond_to :json
def create
#project = Project.find(params[:task][:project_id])
if #task = #project.tasks.create!(name: params[:task][:name], description: params[:task][:description])
respond_with(#task)
else
flash[:error] = 'error'
end
and inside on index.htm.erb I have:
$(document).ready(function(){
$("#new_task").bind('ajax:success', function(evt, data, status, xhr){
var row = ('<tr id="task.id"><td>'+data.name+'</td>'+'<td>'+data.description+'</td>'+'<td>'+data.state+'</td>'+'<td><%= link_to "delete task", task_path(task.id), :method => "delete", remote:true, data: { confirm: "Are you sure?" } %></td></tr>');
$(row).insertAfter("#tasks_table tr:first");
slicer($("#total"));
slicer($("#active"));
}); });
$("#new_task").bind("ajax:beforeSend", function(event,xhr,status){
$(this)[0].reset();
});
And now I'd like to give some explanation about how this might work(my personal thoughts)
by specyfing remote: true, I'm telling the form to submit data in input on server via Unobtrusive Javascript, rails 3 feature. It sends an usual hash in my example this is params[:task][:name] etc, but it expects to get back JSON because I did set :html => {:'data-type' => 'json'}) (is this correct syntax?). Now in my TasksController class I have the respond_to :json, it means that controller WILL answer those requests, where data-type json is specified, with json, because respond_with is smart enough to do to_json on object you are doing respond_with.
Am I right in all those assumptions?
So the question is if we can use js.erb and json.erb(yes?) do you need to return the json at all ?
Maybe going a step back helps.
With AJAX form, the submit event goes through JavaScript first, and does not trigger the browser to invoke a POST '/data.json'.
If you look at the HTML that is generated by Rails with the remote: true option, you probably see a "onsubmit()" or similar callback. It is also the JavaScript, that processes a 'success' or 'error' event. I like the jQuery documentation for this: http://api.jquery.com/jQuery.ajax/
Your question is also somewhat opinionated, because JavaScript libraries such as Backbone.js, Ember.js or Angular, allow to fine-tune the process chain of a form submit even further.

Pass js variable to server code

I have a form with 2 inputs and button. An user put feed url in the first input and press the button:
<%= link_to "get name", { :controller => 'Feeds', :action => "get_title" },
:remote => true, :class=>'btn btn-mini' %>
Here is controller method
def get_title
respond_to do | format |
format.js {render :layout => false}
end
end
And here is a get_title.js.erb:
var url = $( "#feed_url" ).val();
console.log(url);
$( "#feed_name" ).val("<%= #proxy.title(url) %>");
I get value of the first input and want to pass it as parameter to Ruby class. But in this case I get the error:
ActionView::Template::Error (undefined local variable or method `url' for #<#<Class:0x41ec170>:0x41ef968>):
1: var url = $( "#feed_url" ).val();
2: console.log(url);
3: $( "#feed_name" ).val("<%= #proxy.title(url) %>");
Rails this that 'url' is a Ruby variable, but not JS one.
How can I pass JS variable to Ruby code ?
Thanks in advance.
Remember that any ERB (ruby) code is executed server side, while Javascript is, of course, rendered client side. As a result, your line <%= #proxy.title(url) %> is rendered WAY before that url value is ever evaluated. The solution to your situation is more along the lines of passing data to Rails, and rendering the response. Three things to facilitate this (bearing in mind that this is only one approach, and I'm sure there are plenty of others, and possibly better ways of doing this):
1- Your link_to won't post the user-input URL value because it is not POSTing the form. Instead, change the surrounding form to use :remote=true, and use a typical form.submit button rather than this link. Your form with the URL value will be submitted (and it will be asynchronous).
2- In your controller, render your title like you were trying to do, doing something along these lines:
def get_title
render :text=>#proxy.title(params[:url])
end
3- Bind to the ajax:success event, something along these lines:
$("form#myForm").bind("ajax:success", function(event, data, status, xhr){
$( "#feed_name" ).val(data) // data, in this case, is the rendered `#proxy.title(url)` we did in step 2.
})
Hope that makes sense. Let me know if it does not.

Creating a :remote=>true post using AJAX rather than rails

I've spent a day spinning my wheels trying to understand how Rails :remote=>true works. The question below seems complicated but I'm trying to understand this simple question with the information I provide:
How can I make an Ajax call that simply renders as JS without using :remote=>true?
From my understanding :remote=>true simply generates and handles an AJAX call:
My view looks like this:
opts in a very complicated way, creates a link with :remote => true. This is omitted for simplicity
.e_list
= opts.sort_link(:name)
= opts.sort_link(:description)
- e_list.each do |e|
.entry
= link_to(e.name, '#', class: 'select_e', data: {e_id: e.id})
= e.description
= paginate e_list, remote: true, params: {search:"j", com_id: com.id}
Gallery.js.erb
$('#com<%= #com.id %>').replaceWith('<%= escape_javascript render(partial: "shared/com", locals: {com: #com}) %>');
My Controller:
def gallery
if params[:com_id]
#com = #s.com.find(params[:com_id])
#com.filter = params
end
if c = #s.com.where(:_type => "Com").first
#current_e = c.entries(#user.app_id).first
#current_e.og_url = view_context.og_url(#current_e)
end
render :text => "foobar" if !#current_e
end
logs, after the user clicks on the pagination links or sort links (the key is those links have :remote => true)
Started GET "super long url" for 127.0.0.1 at 2012-05-04 16:08:42 -0700
Processing by CController#gallery as JS
SO I TRY TO RECREATE THIS WITH AJAX:
$('button.search').live 'click', (e) ->
search = $(e.target).attr('search-term')
success_callback = (results) ->
if results
console.log(results)
update_components(results[0].entry, '.entry')
else
$.ajax(
url: 'super long url that is exactly the same url as above!'
).done ->
return false
MY FAILED RESPONSE THAT DOES NOT RENDER AS JS, YET I THOUGHT :remote => true was simply an ajax call wtf?:
Started GET "super long url identical as the one that renders JS" for 127.0.0.1 at 2012-05-04 16:07:22 -0700
Processing by ContestController#gallery as */*
What is going on? How can I make an Ajax call that simply renders as JS without using :remote=>true?
Try
$.ajax({
url: 'your url',
dataType: 'script'
})
http://www.alfajango.com/blog/rails-3-remote-links-and-forms-data-type-with-jquery/
try
/screens/4fa02763dc1c82269c0001da/contest/gallery.js?app_row_id=5....
If you want the js from the response to execute in the browser you should need to do something like eval(response), but i'm just suggesting, I never done it and even know how to eval code of a string in javascript.
You could use jQuery to accomplish what you are trying to do:
/* your jquery file */
jQuery.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
})
...
$('#button').click(function() {
$.post('/controller/action', {
query_string1: value1
});
});

Resources