I'm trying to create a function with ajax to edit and update comments in a form.
my edit function is working without problems using ajax but when i try to update the comment, i get the error: CommentsController#update is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []
comments_controller
def update
respond_to :js
authorize #comment, :update?
#comment.update(comment_params)
if #comment.save
flash[:notice] = 'Commentaar is succesvol toegevoegd.'
else
flash.now[:alert] = 'Commentaar is niet toegevoegd.'
end
end
def comment_params
params.require(:comment).permit(:text)
end
update.js.erb
$("#comment-ajax-<%= #comment.id %>").html("<%= j render #comment %>");
_comment.html.erb
<% if policy(comment).edit? %>
<%= link_to 'edit', [:edit, comment.fabmoment, comment], remote: true, 'data-type' => 'script' %>
<% end %>
comment form
<%= simple_form_for [fabmoment, comment] do |f| %>
<!-- Input -->
<%= f.input_field :text, rows: 4 %>
<%= f.label :text %>
<% end %>
The error message is telling you that the form is being submitted as 'text/html'. Try adding remote: true to the actual form instead of the link_to.
Related
I am having a problem with the turbo-rails gem. First I installed the latest version in my Rails 7 application. On my site, I have a select input which is wrapped in a form, with a partial below that form that shows the data. Now I want to apply a filter using the select and dynamically update the data using this turbo-rails package. My form html looks like this:
<div class="users">
<div class="filters">
<%= form_with url: '/users/load', method: :get, data: { turbo_frame: :form_response } do |form| %>
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
options: #genders,
classes: 'filter',
name: 'gender',
} %>
<%= form.submit %>
<% end %>
</div>
<%= turbo_frame_tag :form_response do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
</div>
In my routes, I created this get request which is forwared to a load method in my controller like this:
get '/users' => "users#index"
get '/users/load' => "users#load"
And then in my controller I have the 2 methods written like this:
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#genders = ["Male", "Female"]
#users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
end
def load
#users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
if params[:gender]
#users = #users.where(gender: params[:gender])
end
respond_to do |format|
format.html { render partial: 'users/user_list', locals: { users: #users } }
end
end
end
The problem is that when I go to this page, select a gender and hit the submit button, I get to see the user data with the correct genders, but I only see the partial loaded, so the rest of the page is gone. I can see in the network tab of developer tools in Chrome that the request headers is set to:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
I want to use the turbo-streams instead of the turbo frames, because I need to update more of these items on the same page. Why is it not rendering the content inside the page, instead of rendering the partial only?
How can this be fixed?
To answer your question, you're rendering a partial without turbo stream or turbo frame, so you're only getting a partial as response.
I think, a few examples will explain everything.
# config/routes.rb
resources :users
# app/controllers/users_controller.rb
def index
scope = User.order(created_at: :desc)
scope = scope.where(name: params[:search]) if params[:search]
#users = scope
end
"Preserve log" is quite useful when working with turbo frame and it redirects and clears the console:
https://developer.chrome.com/docs/devtools/console/reference/#persist
Turbo FRAME using GET request with HTML response
We are in index action and the form is submitting back to index.
# app/views/users/index.html.erb
# expect :users_index turbo frame in a response vvvvvvvvvvvvvvvvvvvvvvvvv
<%= form_with url: users_path, method: :get, data: { turbo_frame: :users_index } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
# turbo frame in a response needs to match turbo frame on the current page,
# since we're rendering the same page again, we have the matching frame,
# only content inside this frame is updated.
<%= turbo_frame_tag :users_index do %>
<%= render #users %>
<% end %>
# If you render some other page, you have to wrap it in
# `turbo_frame_tag :users_index`
If you want to update the url as well, so you don't lose the search on refresh:
<%= turbo_frame_tag :users_index, data: { turbo_action: :advance } do %>
<%= render #users %>
<% end %>
Turbo STREAM using GET request with TURBO_STREAM response
You have to set data-turbo-stream="true" to send a GET stream.
# app/views/users/index.html.erb
<%= form_with url: users_path, method: :get, data: { turbo_stream: true } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render #users %>
<% end %>
Add turbo_stream format to respond to this request:
# app/views/users/index.turbo_stream.erb
# update content inside <div id="users_index">
<%= turbo_stream.update :users_index do %>
<%= render #users %>
<% end %>
# add another `turbo_stream` here if you'd like.
Turbo STREAM using POST request with TURBO_STREAM response
# config/routes.rb
resources :users do
# # add `search` action
# post :search, on: :collection
# i'll be lazy and post to :index
post :search, action: :index, on: :collection
end
POST form submissions are sent as TURBO_STREAM by default and it will render index.turbo_stream.erb.
# app/views/users/index.html.erb
<%= form_with url: search_users_path do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render #users %>
<% end %>
# app/views/users/index.turbo_stream.erb
<%= turbo_stream.update :users_index do %>
<%= render #users %>
<% end %>
Test set up
Just do a simple set up:
rails --version
# Rails 7.0.4
rails new turbo-test -j esbuild
cd turbo-test
bin/rails g scaffold User name
bin/rails db:migrate
open http://localhost:3000/users
bin/dev
# app/controllers/users_controller.rb
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
# Add this line:
format.turbo_stream { render turbo_stream: turbo_stream.prepend(:users, partial: "user", locals: { user: #user }) }
format.html { redirect_to user_url(#user), notice: "User was successfully created." }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
# app/views/users/index.html.erb
# submit form
<%= render "form", user: User.new %>
# new user gets prepended here
<div id="users">
<%= render #users %>
</div>
I have a form and a div for showing the message which is sent by form. I have to refresh the page after sending a message by form to see the messages in my div. However, I want to see the messages in div without refreshing the page, like a chatroom. I tried to use Ajax but I couldn't make it.
Here is my form:
<%= form_for #message, remote:true do |f| %>
<%= f.text_area :body, class: "form-control", placeholder: "Enter Message" %>
<%= f.submit "Send", class: 'btn btn-primary btn-lg' %>
<% end %>
And here is my controller
class MessagesController < ApplicationController
def index
#messagesAll = Message.all
#message = Message.new
end
def new
#message = Message.all
end
def create
#message = Message.new(params.require(:message).permit(:user, :body))
if #message.save
respond_to do |format|
format.js {}
format.html {redirect_to messages_url}
end
else
render 'new'
end
end
end
And here is my div:
<div class="panel-body" id="messages-div">
<ul class="media-list">
<% #messagesAll.each do |post| %>
<li class="media">
<%= post.body %>
</li>
<% end %>
</ul>
</div>
Could anyone help me? Thank you.
Assuming you have a div with an id of messages that you're rendering the messages in e.g.
<div id="messages">
<%= render #messages %>
</div>
On your create action rails will look for a create.js.erb file. So what you want is this in your controller:
respond_to do |format|
format.js {}
format.html {redirect_to messages_url}
end
Then have a create.js.erb file:
$('#messages').append('<%= j render #message %>');
Edit: In your specific case you would need to target the ul. So your create.js.erb would be
$('#messages-div ul.media-list').append('<li class="media"><%= j #message.body %></li>');
j is just a shortcut for escape_javascript. Docs
That should get you started, but you'll want to do something else via ajax if the message isn't saved.
I used this tutorial for making polymorphic comments
https://gorails.com/episodes/comments-with-polymorphic-associations
It works fine but when I set remote true to my comments form I got 505 error(the new comment is not appended to the list) when trying to add a new comment.
But when I reload the page the new comment appears in the list.
What can be wrong here?
I got this files in views/comments folder:
create.js
<% unless #comment.errors.any? %>
$("ul.comments").append('<%= j render "comments/comment" %>')
$("#new_comment")[0].reset();
<% end %>
_form.html.erb
<%= simple_form_for([commentable, Comment.new], remote: true) do |f| %>
<%= f.input :body, label: false %>
<%= f.button :submit, "Submit" %>
<% end %>
comments_controller.rb
def create
#comment = #commentable.comments.new(comment_params)
#comment.user = current_user
#comment.save
respond_to do |format|
format.html { redirect_to #commentable, notice: "Your comment was successfully added."}
format.js
end
end
_comment.html.erb
<li class="comment">
<b><%= comment.user.try(:username) %>:</b>
<%= comment.body%>
</li>
console
UP
I got this routes for post
resources :posts do
resources :comments, module: :posts
end
+
controllers/posts/comments_controller
class Posts::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
#commentable = Post.friendly.find(params[:post_id])
end
end
The folder structure looks almost the same as here
https://github.com/excid3/gorails-episode-36/tree/master/app/controllers
the response tab shows this
undefined local variable or method `comment' for #<#<Class:0x007f993d3c5450>:0x007f99450a6190>
Trace of template inclusion: app/views/comments/create.js.erb
and when I replace
<%= j render #comment %> with some text it appends to the list on submit
ok the problem is with _comment.html erb
updated create.js with instance variable for comment and it works now.
<% unless #comment.errors.any? %>
$("ul.comments").append('<%= j render partial: "comments/comment", locals:{comment: #comment} %>')
$("#new_comment")[0].reset();
<% end %>
I'm trying to implement the Best In Place gem and have followed along with the Railscast, but am having issues. I'm learning Rails and am building a sample blog app with two models, Article and Comment. I'm trying to use Best In Place to edit comments.
_list_comments.html.erb
<% #comments.each do |comment| %>
<hr />
<%= link_to article_comment_path(#article, comment), method: :delete, data: {confirm: 'Are you sure?'}, remote: true do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
<%= content_tag :span, '', id: "#{comment.id}", class: "glyphicon glyphicon-edit edit_comment" %>
<!--<%= content_tag :p, content_tag(:small, "#{comment.author}"), id: "comment_author_#{comment.id}" %>-->
<%= best_in_place comment, :author %>
<%= content_tag :p, id: "comment_body_#{comment.id}" do %>
<%= comment.body %>
<% end %>
<% end %>
It is giving me this error: ActionView::Template::Error (undefined method comment_path for #<#<Class:0x007fdc38fb8288>:0x007fdc38fc36b0>):, which is referring to <%= best_in_place comment, :author %>. I'm pretty sure I installed everything correctly, so I don't know what the problem is.
When I change <%= best_in_place comment, :author %> to <%= best_in_place "#{comment}", :author %>, it gives me this error: undefined method 'author' for "#<Comment:0x007fdc3c841820>":String.
comments_controller.html.erb
class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
#comment.article_id = params[:article_id]
if #comment.save
respond_to do |f|
f.html { redirect_to article_path(params[:article_id]), notice: 'Comment created!' }
f.js {
#article = Article.find(params[:article_id])
#comment = #comment
#comments = Comment.where(article_id: params[:article_id])
}
end
else
redirect_to article_path(params[:article_id]), warning: 'Unable to create comment.'
end
end
def destroy
#comment = Comment.find(params[:id]).destroy
respond_to do |f|
f.html { redirect_to article_path(params[:article_id]) }
f.js {
#article = Article.find(params[:article_id])
#comments = Comment.where(article_id: params[:article_id])
}
end
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
respond_to do |f|
f.html { redirect_to article_path(#comment.article_id) }
f.json { render head :ok}
end
else
respond_to do |f|
f.html { redirect_to article_path(#comment.article_id) }
f.json { render json: #comment.errors.full_messages, status: :unprocessable_entity }
end
end
end
private
def comment_params
params.require(:comment).permit(:author, :body)
end
end
I was able to clone your project and figure out why you are getting this error:
undefined method comment_path for #<#<Class:0x007fdc38fb8288>:0x007fdc38fc36b0>
Here is what's happening. In this code <%= best_in_place comment, :author %>, you are passing comment object. best_in_place is trying to map it to a route helper named comment_path by appending path to comment (hint: undefined method comment_path).
Your routes are set up this way:
resources :articles do
resources :comments
end
if you do rake routes, you'll notice that you don't have a helper path named comment_path which corresponds to your comments#show. It is looking for comment_path and it can't find it. hence, the error message.
However, since you are using nesting resources the helper path that corresponds to the comments#show is called article_comment_path. here is the complete mapping:
article_comment_path GET /articles/:article_id/comments/:id(.:format) comments#show
To get best_in_place map to the correct helper path, there are two ways you can solve this:
1) create a route that maps to comment_path by simply adding this route to your routes.rb file:
resources :comments, only: [:show]
The full mapping for the above route is:
comment_path GET /comments/:id(.:format) comments#show
you now have comment_path. No more error message.
2) Tweak your code to map to article_comment_path
replace:
<%= best_in_place comment, :author %>
with:
<%= best_in_place [#article, comment], :author %>
The array, [#article, comment], will build the helper path article_comment_path by appending comment to #article
I've started learning rails and I would like to add ajax to comments
Comments should be added using Ajax
Can you please help or link to an example?
Here if my config.rb:
resources :posts do
resources :comments
end
in post/show.html.erb
<p><%= #post.title %></p>
<h2>Comments:</h2>
<div id='com'>
<%= render #post.comments %>
</div>
<h2>Add a comment:</h2>
<%= render "comments/form" %>
in view/comments/_comment.html.erb:
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<%= link_to "Del", [comment.post, comment], :confirm => 'Are you sure?', :method => :delete %>
<hr>
in comments/form.html.erb
<%= form_for([#post, #post.comments.build], remote: true) do |f| %>
.
.
.
in comments_controller:
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js
end
end
and in view add file : _create.js.erb
$('#com').html("<%=j render #post.comments %>");
without remote true
all works ( but reload page )
action delete works to
in application.html.erb
<%= javascript_include_tag 'application' %>
when i click to submit form - nothing happens
but when i (after submit) reload page - i saw added comment(s)
You need to include unobtrusive javascript
in app/assets/javascripts/application.js
Do you have this (if not add it )
//= require jquery
//= require jquery_ujs
and
Answered by #DemitriyDN himself :)
rename _create.js.erb to create.js.erb
Edit your new code
in comments_controller:
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js
end
end
end