I'm using Rails 4.2.5, I've written a controller test for the destroy action, an I'm using ajax call to destroy and using destroy.js.erb file. Please help me to solve the following issue to pass the test when it calls js format, I'm pasting error below.
def destroy
#status = #song.destroy
respond_to do |format|
format.js
end
end
SongsControllerTest#test_should_destroy_song:
ActionController::UnknownFormat: ActionController::UnknownFormat
app/controllers/songs_controller.rb:36:in `destroy'
songs_controller_test.rb
test "should destroy song" do
assert_difference('Song.count', -1) do
delete :destroy, id: #song
end
get :destroy, format: 'js'
end
destroy.js.erb
var element = document.getElementById("<%="song#{#song.id}" %>");
<%if #status%>
element.parentNode.parentNode.remove();
<%end%>
The destroy controller action normally reacts to the DELETE HTTP method, so you should use the format option when calling delete:
test "should destroy song" do
assert_difference('Song.count', -1) do
delete :destroy, id: #song, format: :js
end
end
Related
I am searching for the internet about this topic, but I don't get how AJAX works with rails, I already check the documentation and I just simply don't understand it.
What I know is that AJAX is asynchronous and it just only takes to put this code in the view for make the request: "remote:true", the big deal that I don't get it is about this code:
respond_to :js
Apparently, it tells the controller that it would respond to Javascript and you have to make a file for whatever you wanna do with JS, my structure of my project is this one:
View
New.html.erb:
<p>Imagen de portada</p>
<%= simple_form_for #entries, remote: true do |f| %>
<% f.file_field 'input-image' %>
<% end %>
<div id="image-entry"></div>
View route:
views
|
-->admins
|
-->entries
|-->new.html.erb
|-->new.js.erb
Controller
entries_controller.rb
module Admins
class EntriesController < ApplicationController
before_action :authenticate_admin!
def index
render 'index'
end
def new
#entries=''
render 'new'
respond_to :js
end
end
end
Controller route:
controllers
|
-->admins
|
-->entries_controller.erb
JS
new.js.erb
console.log('funciona');
$('#input-image').click(function(){
$('body').css('background-color', 'red');
});
JS route:
views
|
-->admins
|
-->entries
|-->new.html.erb
|-->new.js.erb
Error
My error in this code is the following:
ActionController::UnknownFormat
So, I have questions about this error and what is the correct name of the file in JS for get the respond_to work correctly.
Answer by Irfan Fadilah on Facebook:
Your request is not processed by "new" method in EntriesController. The default form method is POST, so Rails will looking for "create" method in you EntriesController.
You can read RESTful routing in Rails Guide for more details.
In order to make your AJAX request to works. Add "create" method in EntriesController and create.js.erb (just write alert("Hello"); or something to test it) in views/entries.
I have looked at dozens of stack overflow posts and haven't found a solution that works which is why I'm reaching out for an otherwise well documented use case.
I have a button that that should do this.
When clicked, call a custom controller method that updates the model and does other things
Call a javascript function to update the page without reloading it (ajax)
Right now, the only way I am able to call a custom controller method is via this way which feels really hacky. I have stripped this down to as simple as possible.
routes.rb
match 'admin/:id/toggleAdmin' => 'admin#toggleAdmin', via: [:patch, :put], as: :toggleAdmin
list.html.erb
<td><%= link_to "Toggle Admin", toggleAdmin_path(id: user.id), method: :patch %></td>
admin_controller.rb
class AdminController < ApplicationController
def toggleAdmin
idToToggle = User.find(params[:id]).id
if idToToggle == current_user.id
redirect_to admin_list_path, danger: "You tried to make yourself a normal user! Don't do that!"
else
User.find(params[:id]).updateToAdmin()
redirect_to admin_list_path, info: "The user with an ID of #{idToToggle} has had their admin attribute toggled!"
end
end
end
What I would like to do instead of reloading the page when an admin is toggled is to just use some javascript to rewrite that part of the dom.
What is a better way to go about this?
Here are just a few of the various resources I have already tried.
How to call a controller's method from a view?
Can we call a Controller's method from a view (as we call from helper ideally)?
Call controller method with rails-Ajax?
https://www.reddit.com/r/rails/comments/7iyp6g/want_a_button_to_call_a_method_apparently_this_is/
How do I call a JavaScript function from an html.erb
Thanks for the help.
--- Edit for more information.
Using a clean rails app I now have the ability to call a controller method more cleanly but I am not getting an ajax request to go through that updates the page to show that the action was completed. (I am expecting a boolean value change and a flash). Here is the following relevant code:
users.js
$("#edit-form").html("<%= j render partial: 'form', locals: { user: #user } %>")
_form.html.erb
<%= form_for user do |form| %>
<%= user.admin %>
<% end %>
<%= link_to "Toggle Admin", toggle_admin_user_path(user), method: :put, remote: true %>
edit.html.erb
<h1>Editing User</h1>
<div id="edit-form">
<%= render partial: 'form', locals: { user: #user } %>
</div>
<%= link_to 'Show', #user %> |
<%= link_to 'Back', users_path %>
users_controller.rb Note that I have only included the toggle_admin and a couple other methods as the rest are just scaffolding.
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy, :toggle_admin]
def toggle_admin
if 1 == 1
logger.info "This is from info"
u = User.find(params[:id])
u.admin = !(u.admin)
u.save
respond_to do |format|
format.js { flash[:info] = "The user with an ID of #{#user.id} has had their admin attribute toggled!" }
end
else
redirect_to admin_list_path, danger: "You tried to make yourself a normal user! Don't do that!"
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.fetch(:user, {})
end
end
The routing
First thing, if you need a nested custom route inside a resource you can define it as a member of this resource:
resources :users do
put 'toggle_admin', on: :member
end
which returns:
toggle_admin PUT /users/:id/toggle(.:format) users#toggle
Second, your method names should be in snake_case : def toggle_admin
See "Instance Methods" section
The controller
If you just want to update a user to admin (guessing by this method here: User.find(params[:id]).updateToAdmin()), you can define the custom toggle_adminmethod inside the UsersController.
Your custom method for triggering ajax request should respond to the js format and point to the corresponding js view (toggle_admin.js.erb)
As a member of the resource User, you can add it to the before_action callback to return the right user when you call the method.
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy, :toggle_admin]
#...
def toggle_admin
if #some condition
# do some logic here
respond_to do |format|
format.js { flash[:info] = "The user with an ID of #{#user.id} has had their admin attribute toggled!" }
end
else
redirect_to admin_list_path, danger: "You tried to make yourself a normal user! Don't do that!"
end
end
end
The view
In rails views, when you want to use ajax to update a view without reload, you need to use partial. It's this partial which will be refreshed inside the .js.erb file on a specific DOM element.
Let's make an example for the user#edit view.
First you need to render the view inside a partial form for example, and wrap it into a div with specific id:
edit.html.erb
<div id="edit-form">
<%= render partial: 'form', locals: { user: #user } %>
</div>
The partial:
_form.html.erb
<%= form_for user do |form| %>
# the form
<% end %>
<%= link_to "Toggle Admin", toggle_admin_user_path(user), method: :put, remote: true %>
# don't forget the remote: true for the ajax call
Finally the .js.erb for the custom action (assuming you have jquery):
toggle_admin.js.erb
$("#edit-form").html("<%= j render partial: 'form', locals: { user: #user } %>")
And voilĂ ! when click the link, you refresh the edit view with new informations from the controller without reloading the view.
Of course, this is an example for a classic resource's edit view but you can adapt it for any case. Just keep the good naming between routes, controllers, controller methods and views (AdminController, list view etc...)
Edit
To handle the flash messages, you need to add inside your layout > application.html.erb :
<body>
<% flash.each do |key, value| %>
<%= content_tag :div, value, class: "classname" %>
<% end %>
<%= yield %>
</body>
I'm trying to understand AJAX requests in Rails. I have a form that I currently submit using remote: true. I want to respond with an HTML redirect if the request is successful, and run an error message with Javascript if it is unsuccessful. However, no matter what the outcome is, the request seems to expect a .html as the return.
respond_to do |format|
if conversation
format.html { redirect_to(conversation_path(conversation)) }
else
format.js
end
end
This is called after I save the conversation call on AJAX. On a successful save, the path HTML is correctly sent back, but is not rendered on the client. But on an unsuccessful save, it expects the .html and throws an error. How do I accept .js as a response? My goal is to just pop up an error if the call is unsuccessful and redirect_to on a successful call.
Edit: My form_for:
<%= form_for :conversation, url: :conversations, remote: true, html: { class: "conversation-form" } do |f| %>
Here's a suggested alternative to your end-goal - in the controller, drop the format.html entry in your respond_to block. Also, set conversation to an instance variable that the view template can access:
class YourController < ActionController::Base
def your_action
# awesome code doing stuff with a conversation object goes here
#conversation = conversation
respond_to do |format|
format.js # your_action.js.erb
end
end
end
Then, put the redirect logic in your javascript view template (for the above example: app/views/.../your_action.js.erb):
<% if #conversation.errors.any? # or whatever condition you want to show a popup for %>
// put your javascript popup code here
alert('Errors happened!');
<% else %>
// this is how you do a redirect using javascript:
window.location.href = "<%= conversation_path( #conversation ) %>";
<% end %>
Hope this helps!
I want to an ajax request to call user controller's xyz action with the passed parameter stage and then append it on #the_box so that I can perform conditional operation like for stage x append #user.age, for stage y append #user.age+1 and for stage z append #user.age-1. How should I do?
I have a html.erb file contains such code:
<center>
<%= link_to "A", xyz_user_path, stage: "x", remote: true %>
<%= link_to "B", xyz_user_path, stage: "y", remote: true %>
<%= link_to "C", xyz_user_path, stage: "z", remote: true %>
</center>
<div id="the_box"></div>
and users_controller.rb has
before_action :set_user, only: [ :xyz]
def xyz
respond_to do |format|
format.js {}
end
end
private
def set_user
#user = User.find(params[:id])
end
and this xyz.js.erb is what I have now but cannot deal with parameter so it cannot handle conditional operation
$("#the_box").append("<%= #user.age%>");
The other answers will help you on here - let me explain them
format.js
When you use format.js to filter the mime types, opening brackets basically tells Rails that you want to perform some custom functionality (and hence it won't load the [action].js.erb as default)
If you want to just invoke [action].js.erb, you'll have to call the following:
respond_to do |format|
format.js #-> will just call [action].js.erb
end
You'll want to read up more about Rails mime types (how to determine XHR requests) & the respond_to block
#instance_variables
Secondly, you need to consider the role of #instance variables in your application, and consequently, the method in question.
The main issue you have is you're calling #user in a method which doesn't define it - hence you'll likely see the error undefined method ___ for nil:NilClass or similar
You must remember that Rails is just a gem - a series of classes which runs on the Ruby language. This means that each time you wish to perform a method on an "object" / piece of data, you'll have you to declare / create the object in your Rails application:
def xyz
#user = User.find params[:id] #-> sets the variable for the duration of the "instance"
...
end
Please take note of the name of the variable -- an instance variable. By setting #user (instance variables are always defined with an #), you basically give Rails the ability to access the data inside it for as long as the class which defines the variable is invoked.
Basically means that if you set #user = User.find, it will be available through your view & other helper methods called from the instance of the class
Fix
Finally, to address your question directly:
I want to an ajax request to call user controller's xyz action with
the passed parameter stage and then append it on #the_box so that I
can perform conditional operation like for stage x append #user.age,
for stage y append #user.age+1 and for stage z append #user.age-1. How
should I do?
#config/routes.rb
resources :users do
get "xyz/:stage" #-> domain.com/users/:user_id/xyz/:stage
end
#app/views/users/your_view.html.erb
<% links = [["A", "x"], ["B", "y"], ["C", "z"]] %>
<% links.each do |link, stage| %>
<%= link_to link, xyz_user_path(user, stage: stage), remote: true %>
<% end %>
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def xyz
case params[:stage]
when "x"
#user.increment!(:age)
when "y"
#user.decrement!(:age)
end
respond_to do |format|
format.js
end
end
end
#app/views/users/xyz.js.erb
$("#the_box").append("<%=j #user.age %>");
You should pass the parameter in the helper like this:
xyz_user_path(stage: "x")
And then in your js.erb you can access it like:
<%= params['stage'] %>
Here I'm going to access an instance variable defined in wish_me action.
controllers/jserb_controller.rb code:
class JserbController < ApplicationController
def index
end
def wish_me
#test = 'Hi how are you?'
respond_to do |format|
format.js
end
end
end
/views/jserb/index.html.erb code:
<div class="wish-me"></div>
<%= link_to "Wish Me", wishme_path, remote: true %>
/views/jserb/wish_me.js.erb
$(".wish-me").append("<%=j #test %>");
config/routes.rb code:
match '/index' => 'jserb#index'
match '/wishme' => 'jserb#wish_me', :as => :wishme
Now try to hit http://<domain-name>/index and then click on WishMe
link. So now you are able to pass instance variable to js.erb and accessed the same.
I have spent a couple of days (like 4) trying to solve this issue. I followed the Hartl Rails 3 tutorial and in chapter 12 tried to convert the site from prototype to jQuery. I am not able to get the "follow"/"unfollow" button to update however.
I am able to issue jQuery commands from within Safari's Inspect Element Console, but if I put even the simplest jQuery command into the destroy.js.erb or create.js.erb files, nothing happens. The log is indicating that the appropriate relationships/destry.js.erb (or create.js.erb) file is rendering.
Here is the code that I have in the controller:
class RelationshipsController < ApplicationController
before_filter :authenticate
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
The users/_follow.html.haml is
- relationship = current_user.relationships.build(:followed_id => #user.id)
= form_for(relationship, :remote => true) do |f|
= f.hidden_field :followed_id
.actions= f.submit "Follow"
The users/_unfollow.html.haml is
- relationship = current_user.relationships.find_by_followed_id(#user)
- delete = { :method => :delete }
= form_for(relationship, :html => delete, :remote => true) do |f|
.actions= f.submit "Unfollow"
The users/_follow_form.html.haml is
- unless current_user?(#user)
#follow_form
- if current_user.following?(#user)
= render 'unfollow'
- else
= render 'follow'
The relationships/create.js.erb is
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
$("#followers").html('<%= "#{#user.followers.count} followers" %>')
The relationships/destroy.js.erb is
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
$("#followers").html('<%= "#{#user.followers.count} followers" %>')
However, in trying to diagnose this, I tried a very basic
$("#title").html("Followed")
which also does not work.
Looks like an issue with how you're using jQuery.
jQuery uses CSS-style selectors, while Prototype doesn't. So, to make it work, just add a "#" symbol to the selectors.
Instead of
$("follow_form").html("something");
You should use
$("#follow_form").html("something");
// Note the # symbol in the selector.
// Also, remember to end your statements with a semicolon.
You can read about jQuery ID selectors here: http://api.jquery.com/id-selector/
Check your public folder for an Assets folder, with application.js in it. Not sure how I ended up with that, but that was causing ajax queries to be processed twice.
It looks like you might be using Rails 3.1. It's important to use the exact gem versions used in the tutorial (including Rails 3.0 instead of 3.1) if want the same results. See the debugging tips at the Rails Tutorial Help page for more suggestions.