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.
Related
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
I posted a very poor question about this earlier, so I am reposting and making it MVCE.
I'm building a messaging service with Rails and AJAX. So far I can submit a message through a form, it will update in the HTML DOM, an AJAX POST method will send it to the controller, and the controller will save it in the database.
Now I need to add an AJAX method that will GET the message that was just submitted -- so that other users (in other browsers) will be able to view it.
Currently, and this is a hack job way of doing it, in my JS code I set a timeout that calls an AJAX GET function every half second. Is there a better way to do this -- as in, once the controller saves the message can it call the AJAX function? The AJAX code looks like this:
function retrieveMessages(){
var message;
<%debugger%>
$.ajax({
type:"GET",
url:"<%= messages_get_path %>",
dataType:"json",
data: { what_goes_here: "blah" }, //this is the part I do not understand -- see below
success:function(data){
message = data;
console.log(data)
}
});
setTimeout(retrieveMessages, 500);
}
$(document).ready(function(){
//get messages
setTimeout(retrieveMessages, 500);
... more irrelevant
The line data: { what_goes_here: "blah" } doesn't make sense to me. What is the syntax for the controller to send data back to be stored into data:? Furthermore, from the console I can see that what_goes_here is being passed as a parameter to the controller -- again this doesn't make sense to me.
My route looks like this get 'messages/get', :to => 'messages#get' (this might be incorrect?)
rake routes shows
messages_get GET /messages/get(.:format) messages#get
And as of now, I don't have anything in my controller other than a respond_to because at this point I'm just trying to call the controller. What is the syntax to send data back to the AJAX method?
def get
debugger
respond_to do |format|
format.html
format.json {render json: #variable} //is this #variable being passed to the AJAX call?
end
end
UPDATE
This makes more sense to me... the AJAX method simply calls the def get function. The def get function then finds the message in the database, and stores it in an instance variable. Subsequently, I can add some Javascript code that will insert it into the DOM. However I must have something wrong in my routing because I'm getting (in the console) http://localhost:3000/messages/get 404 (Not Found)
What you are doing, as you suspect, is not effective. The more users are online the more will be load from these refreshing requests, most of them probably returning no new data.
You should consider more active way of notifying your browsers about changes on the server. One option is to use ActionCable.
I want to use my controller variable in my javascript.
Since this cannot be done straightforward. I use gon gem. https://github.com/gazay/gon
With this what I do is in a before filter of my base controller (Which acts as a before filter for all the controllers) I do gon.variable_name = value And in my js file I use gon.variable_name. This works fine for full page reloads.
But the variable is not getting updated for ajax request.
Say for example:
On page reload, in my controller I do
gon.variable_name = value1
and in my js, gon.variable_name gives me value1.
After a ajax request in my controller I do
gon.variable_name = value2
and in my js, I still see gon.variable_name as value1 only.
Is there any way I could update gon.variable_name in a ajax request?
Thanks
You can use the watch: true switch for gon:
#app/views/layouts/application.html.erb
<head>
<%= include_gon(watch: true) %>
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def update_vars
#value1 = User.count
gon.watch.value1 = #users_count
end
end
This is what the documentation says. I don't agree with it, but should work.
It looks like ajax polling which is notoriously inefficient.
--
To give you some context, you have to remember that gon basically creates a small hash of javascript in your layout, populating it with the gon variables you've assigned in your controller:
<script>
gon = {
value1: "test",
value2: "test"
}
</script>
We've used it before here:
The reason this is important is because this gon JS variable is invoked at runtime -- meaning that it remains static until you reload the page.
The way around this is to either dynamically reload the gon variable values (using watch above) or pass the new value through your ajax request (like Sergio recommended in the comments):
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def update
respond_to do |format|
format.json { render json: {value_2: "test"}.to_json %>
end
end
end
You'd then be able to use it as follows:
$.ajax({
url: "/update",
success: function(data) {
var value2 = data.value2;
}
});
I have to initialize gon for every request inorder to update the variables.
include_gon(:init => true)
in the response of every request solved the problem.
Extra tip:
For those who use ajaxify rails gem you can call this helper method in the ajaxify_extra_content method and in the content_inserted callback you can append this to the head tag to get it work in a generic way.
Thanks all for your suggestions and help.
There are two ways I know of to pass variables defined in a controller (/action) to JS...
The official way is
.js.erb:
var banana = "<% #banana %>"
Another way (that I'm currently using is)
.html.erb
<span id="banana-variable" style="display:none"><% #banana %></span>
.js
var banana = $("#banana-variable").html()
This js file is loaded on multiple actions/views across the controller. It makes sense to me to not use a .erb extension: users cache it the first time they hit any action/view in the controller. They then won't have to download different versions of the file when they browse to different pages. Am I right?
Yes, are right. The javascript will be cached on the client's browser.
Still you want to send data on the 'js' or script files you can use this gem called Gon.
http://railscasts.com/episodes/324-passing-data-to-javascript
I recommend you use gem 'gon', which is thoroughly introduced in RailsCast. It makes your controller cleaner. Your method will make it more troublesome if you're trying to pass an array or hash to js.
I think your issue is that you are using <% %> which will execute the code, instead of <%= %> which will execute the code and render the result back into the template.
With Gon you can access the page only after the html page is loaded. From what I understand it uses web page as a proxy to transfer data from rails server to javascript.
But if you want to use the variable at any point of time you want, you can do an ajax(post) request from javascript to the rails controller and retrieve the value as json.
Example:
AJAX Request:
$.ajax({
type: "POST",
url: <PageURL>,
async: false,
dataType: 'application/json',
success: function(data){
data = JSON.parse(data);
},
});
your "data" will contain the returned value.
On the controller side it should be:
Controller:
def <PageURL>
render :json => {:result => <value_you_want_to_return>}
end
Do not forget to define the controller's path in routes.rb file.
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.