Rails Ajax Call to Controller Needs Record - javascript

I am using Rails ruby 6.1.4 and ruby 2.6.7
I have a form partial that is used for both the new and edit views. There are two select drop-down form elements. The application.js code makes an Ajax call to the controller to get items to populate the 2nd drop-down based on what is selected in the 1st.
For the new view, my code works fine. But, when viewing a record in the edit view, it does not work. It seems to need an id in the path when on the edit view.
When using developer tools in my browser, the console window shows this error when on the edit view:
[XHR] GET http://localhost:3000/fitness/weights/24/exercises?muscle_group=SHOULDERS
As you can see, it wants the id in the path. But I do not need the id to get the exercises for the drop-down. And of course, the 2nd drop-down does not populate.
How can I adjust my route so both the new and edit views work correctly? OR, do I need to change my ajax call? NOTE: when I move the :exercises out of the collection in the routes, then the reverse happens; the new form does not work but the edit form does.
Here is my code:
application.js:
// if edit view is showing
// get the current value of the Muscle Group field
//
var current_muscle_group;
if( $('#fitness_weight_muscle_group') ){
current_muscle_group = $('#fitness_weight_muscle_group').val();
}
if( current_muscle_group != '' && current_muscle_group != 'Select One' && current_muscle_group != null ){
get_exercises(current_muscle_group);
}
// user selects muscle_group from drop-down
// new or edit form
//
$('#fitness_weight_muscle_group').change(function(){
get_exercises($(this).val());
})
// get_exercises
// ajax call to controller#exercises
// to get exercies for the muscle_group
//
function get_exercises(current_muscle_group){
$.ajax({
url: "exercises",
dataType: "json",
data: {muscle_group: current_muscle_group},
success: function(data){
populate_exercise_select(data);
}
});
}
...
Controller
...fitness/weights/controller:
protect_from_forgery except: [:exercises, :past_exercise, :max_weight]
...
def exercises
# run sql to get exercises for muscle group passed in
if params[:muscle_group]
#exercises=Fitness::Weight.select("DISTINCT exercise")
.where(admin_user_id: session[:user_id])
.where(muscle_group: params[:muscle_group].upcase)
return render json: #exercises
end
...
My routes
config/routes:
...
resources :weights do
collection do
get :exercises
...
end
## added this route to satisfy issue with ajax call
## ... to controller action that requires a record id
get :get_edit_exercises
end
...
Solution
I added a new route (see above) to solve the ID in the path issue when viewing the edit form for a record. I added a controller#get_edit_exercises action to match the route. It returns #exercises just like the def exercises does.
I changed application.js to call the new controller#get_edit_exercises action when it was an edit view. If an ID is in the path/url, then it is an edit view.
application.js
// get the exercises
function get_exercises(current_muscle_group){
var url = "exercises";
var current_record_id = get_current_record_id(); // function below
if( current_record_id > 0 ){
// get_exercises is for edit form. it expects an ID
url = "/fitness/weights/" + current_record_id + "/get_edit_exercises";
}
$.ajax({
url: url,
dataType: "json",
data: {muscle_group: current_muscle_group},
success: function(data){
populate_exercise_select(data);
}
})
}
function get_current_record_id() {
var pathname = $(location).attr('pathname');
return pathname.replace(/[^0-9]/g,'');
}
Note: the id was showing twice in the path, but using a full path in url: url solved that. The forward / was also needed.
url = "/fitness/weights/" + current_record_id + "/get_edit_exercises";```
I also added the new ```controller#get_edit_exercises``` to the controller's ```protect_from_forgery except: [...]```
Everything works. I just need to DRY up some code now.

In words, we have a muscle_group that has_many exercises, (and perhaps an exercise has_many muscle_groups), if it's a bidirectional has_many, then it's achieved with muscle_group has_and_belongs_to_many exercises and vice-versa.
So to populate the exercises drop-down, for a given muscle_group, I would suggest an ExerciseController with an index method that can accept a muscle_group id parameter and respond to an ajax request with a list of exercises.
So the routes would be:
resources :muscle_groups do
resources :exercises
end
and the controller:
class ExercisesController < ApplicationController
def index
#exercises = Exercise.joins(exercise_muscles: :muscles)
.where(muscles: { id: params[:muscle_group_id] })
render #exercises # assumes a partial file '_exercise.html.erb' produces the option tag for an exercise dropdown
end
end

Related

Ruby on Rails - Adding data to model from Javascript

I'm trying to implement a tag/category system using tag-it, but I'm unsure the best method I should be using to pass the data back to the model.
Currently what I'm trying to do is using ajax to call add_tag and pass the tagLabel.
// application.js
$("#tags").tagit({
beforeTagAdded: function(event, ui) {
// do something special
$.ajax({
url: "add_tag",
data : {name: ui.tagLabel }
});
}
});
And within my controller, I just add the param and save it.
// posts_controller.rb
def add_tag
#post.category_list.add( params[:name] )
#post.save
end
While this works for existing posts, I can't use this method for new posts.
And it makes my routes.rb file cluttered if I plan to use this method on multiple models.
// routes.rb
get '/posts/:id/add_tag' => 'posts#add_tag', as: :post_add_tag
Thanks for your help.

How to write a conditional statement amidst this jQuery script?

I have a jquery function which targets a rails form and upon the submit event posts the form data to the appropriate action. The object that’s created, a review, is then returned and via a mustache template is appended to a selected element and rendered:
$(document).ready(function(){
$('.new_review').on('submit', function(event){
event.preventDefault();
var reviewList = $(this).siblings('ul’);
$.post($(this).attr('action'), $(this).serialize(), function(review){
var newReview = Mustache.render($('#review_template').html(), review);
reviewList.append(newReview);
});
});
});
I have in my reviews controller written an if else statement in order to restrict a user (a devise model) to only being able to create one review:
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’
else
#review.user = current_user
#review.save
redirect_to '/restaurants' unless request.xhr?
end
end
However I now need to insert a conditional into my jquery to ensure that it only attempts to append a new object if a review object is actually returned after the post action. I’m thinking that this needs to be written into the third argument on the post function but am a bit lost as to how to go about it.
Could anyone whether this is indeed the right way to go about what i’m trying to achieve and how exactly I could write this. Thanks in advance from a JS newbie.
The below now works perfectly with one simple if statement in the post function:
$(document).ready(function(){
$('.new_review').on('submit', function(event){
event.preventDefault();
var reviewList = $(this).siblings('ul');
$.post($(this).attr('action'), $(this).serialize(), function(review){
var newReview = Mustache.render($('#review_template').html(), review);
if review
reviewList.append(newReview);
});
});
});

AJAX request can't get instance variable from controller in Rails application

I'm trying to get data from my controller into a javascript file in a rails application. The html immediately invokes an ajax request using the select option and the organization id as parameters.
In a before_filter I have
def set_org_id
if params[:id].present?
#org_id = klass.find(params[:id]).id
else
#org_id = 0
end
end
And in the js file I have:
$.ajax({ url: "/admin/analytics/filter",
type: 'GET',
data: {
time_frame: selectedId,
organization_id: <%= #org_id %>
}
})
If I hard code a number as the organization_id everything works fine, but when I try to pass the data from the before filter, I end up with no id in the page.
I should add that the index route is admin/analytics/. The filter action is admin/analytics/filter. The user never navigates to that page, only ajax hits the filter route to get relevant data. Could this be that the ajax request is being send before the instance variable is set? If so, what is the proper solution?
Your JS won't be able to access your #instance variablesunless you call them from your controller itself. The problem here is that if you're loading the ajax to access an instance varialbe - which simply won't work.
Let me explain...
JS
Javascript is known as a client side language - meaning it provides you with the ability to access elements in your HTML / DOM with relative impunity. The problem here is that Rails / Ruby, much like PHP, is server-side, and consequently is only able to provide rendered data to your browser
This means that calling the following simply won't work:
data: {
time_frame: selectedId,
organization_id: <%= #org_id %>
}
As explained, the reason for this is that you cannot access the #org_id from your Javascript. Firstly, you don't know if the #org_id variable will be set (in case you want to use the Rails helpers), and secondly, your JS won't be able to access the variable anyway (because it's Rails)
--
Fix
The purest fix for this is to somehow create the data you need in the DOM. I see from your answer that you have set a hidden field for your form. A much better way to do this is to set an HTML5 "data" attribute, or to use an id
You'd be better doing this:
<%= form_tag route_path, data: { org_id: #org_id } %>
This will give you the ability to call:
$(document).on("submit", "form", function(){
$.ajax({
...
data: {
time_frame: selectedId,
organization_id: $(this).data("org_id")
}
});
});
I was able to solve this by passing the data to a hidden field in the html.erb page.
<%= hidden_field_tag('org_id', #org_id) %>
Then in the javascript refer to the data through the selector.
organization_id: $('#org_id').val()
I don't love this, but at least it works.

How to pass javascript variable to rails erb

Ok, I know the answer is AJAX call, but I have a particular problem:
Everything happens at my homepage in 2 steps:
1- The main user access the root "/"
2- at my index page I have a JS that get his current location
(latitude & longitude) AND reloads the page with the information in
hands with NO user interaction - then showing the page correct.
The problem: I don't know how to make an ajax call doing this ( I don't want to reload all the layout doing window.location ), I want just the index.html.erb, got it? But it's too hard man, I'm on this like a month and can't do it :( Please, someone help me, I really need. Thanks a lot.
You don't need to touch the controller for that. In your erb file, something like this could work:
<script>
if (<%=params[:geo_location].blank? ? 'true' : 'false' %>) {
locationData = DO_YOUR_THING_HERE;
$.get('/', { geo_location: locationData});
}
</script>
This way, when the page first loads, an ajax request will be sent with the parameters.
After the page loads in the second time, the location data will be accessible from the erb file from the params[:geo_location] object.
If you are just willing to refresh a part of the page, you can do something like:
$('#container').load('/A_NEW_CONTROLLER_PATH', {geo_location: locationData});
and set up a A_NEW_CONTROLLER_PATH to return only the partial HTML your interested in. you can do that using render 'index', layout: false in your controller action.
When you retrieve the current location, you have a callback in Javascript, that tells you that everything is finished. Inside this callback, you have access to the position in Javascript, right?
Then you could just post an AJAX request to a route in your Rails app. For example:
routes.rb
post "/user_position" => "position#update"
position_controller.rb
class PositionController < ApplicationController
def update
puts params
# here store the params in your db, or do whatever you want
end
end
JS:
function successGeoCallback(data) {
$.post("/user_position", { lat: data.latitude, lng: data.longitude })
.done(function(data) {
alert("Position was stored" + data);
});
}

Posting to controller using JQuery drop-down box in Rails 3.1

I have the following HTML dropdown box on the index view of the Search controller. I want to update the view based upon the dropbox selection. I need to pass the selection to the controller somehow.
<!-- index.html rendered from Search/Index -->
<select id="search_params" name="search[params]">
<option value="tacos">tacos</option>
<option value="pizza">pizza</option>
</select>
Search Controller code takes params and spits out a message
def index
query = params[:search]
msg = "Your favorite food is #{query}! OMGWTFBBQ!?!?!?!"
respond_to do |format|
format.html
format.js
end
end
This Javascript should somehow post the dropdown box choice to the controller upon selection:
$("#search_params").change(function() {
var state = $('select#search_params :selected').val();
if(state == "") state="0";
//Send the selection to the controller Search controller somehow
// and then render a new view immediately ?
//I AM NOT GOOD WITH COMPUTER
})
return false;
});
I've looked at dozens of other Rails + JQuery examples, jquery API docs, etc. Can't find an answer to the simple question or I'm just overthinking it.
Anyways, thanks in advance for your help!
~Dan
I'm no Ruby/Rails guy but I know jQuery.
I think you want something like
$("#search_params").change(function() {
var state = $('select#search_params :selected').val();
if(state == "") {
state="0";
}
//Call jQuery AJAX
$.ajax({
url:'/path/to/your/controller/action',
type:'POST',
data:'search=' + state,
success: function(data) {
alert(data);
},
error: function() {
alert('Stuff went wrong');
}
});
return false;
});
Hope this helps, and have a read of the jQuery AJAX doc page.
I'm not really sure how you're returning your data from the controller there either? Is there a corresponding view that renders msg?
Checkout jQuery.ajax() documentation: http://api.jquery.com/jQuery.ajax/
You might need to put this at top of application.js (or this might just be a Rails 2.x requirement):
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults
jQuery.ajaxSetup({
'beforeSend': function(xhr){
xhr.setRequestHeader("Accept", "text/javascript");
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
}
});
I set jQuery.ajax() to expect raw javascript code in the server response.
I have my normal html template render a partial. Or, all HTML is abstracted to a partial, not a template.
The javascript template will render basically do this:
$("div#some_id").html(
<%= escape_javascript(render(:partial=>'partial_name')) %>
);

Categories

Resources