Turbo stream in Rails - ruby-on-rails

So, I have a turbo frame in the show view of my Rental:
views/rentals/show.html.erb
<%= turbo_frame_tag "new_rate", src: new_rental_rate_path(#rental) %>
<div id="rates<%= year %>">
<%= render #rates.where("DATE_PART('year', firstnight) = ?", year) %>
</div>
views/rates/new.html.erb
<%= turbo_frame_tag "new_rate", do %>
<%= render "form", rate: #rate, vrental: #vrental %>
<% end %>
views/rates/form.html.erb
<%= simple_form_for [vrental, rate], id: dom_id(rate, "form") do |f| %>
controllers/rates_controller.rb
def create
#rate = Rate.new(rate_params)
#rate.rental = #rental
if #rate.save
flash.now[:notice] = "You've created a new rate."
render turbo_stream: [
turbo_stream.prepend("rates#{#rate.firstnight.year}", #rate),
turbo_stream.replace("new_rate", partial: "form", locals: { rate: Rate.new }),
turbo_stream.replace("notice", partial: "shared/flashes"),
]
else
render :new, status: :unprocessable_entity
end
end
So, here's the problem.
On page load, the new rate form is correctly rendered inside a turbo frame:
<turbo-frame id="new_rate" src="http://localhost:3000/rentals/1/rates/new" complete="">
<form id="new_rate" [...]>
</form>
</turbo-frame>
However, after submitting and correctly saving the first rate, the form is then displayed outside the turbo frame:
<form id="new_rate" [...]>
</form>
I can see that it's happening because I'm replacing the div with "new form" id by partial: "form", which is not wrapped in a turbo frame, but I can't figure out how to fix it..

The most simplistic solve for this is to add a partial that loads the form. but to keep it DRY, make sure to render that partial from within your new.html.erb file.
Practically, it will look like this.
views/rates/new.html.erb
<%= render "new" %>
views/rates/_new.html.erb
<%= turbo_frame_tag "new_rate", do %>
<%= render "form", rate: #rate, vrental: #vrental %>
<% end %>
app/controllers/rates_controller.rb
def create
#rate = Rate.new(rate_params)
#rate.rental = #rental
if #rate.save
flash.now[:notice] = "You've created a new rate."
render turbo_stream: [
turbo_stream.prepend("rates#{#rate.firstnight.year}", #rate),
turbo_stream.replace("new_rate", partial: "new", locals: { rate: Rate.new }),
turbo_stream.replace("notice", partial: "shared/flashes"),
]
else
render :new, status: :unprocessable_entity
end
end

Related

Rails 7 turbo-stream broadcast to specific channel not working

I have a Post model and Comment, and Post has_many Comment and I wanna real-timely update the comment when you are in a specific post, meaning, the comments live-updating should only visible when multiple users are in the same post, for example: in posts/6
For View I have:
<div>
<p>
<strong>Title:</strong>
<%= post.title %>
</p>
<p>
<strong>Content:</strong>
<%= post.content %>
</p>
<p>
<strong>User:</strong>
<%= post.user.username %>
</p>
</div>
<hr/>
<%= form_for [post, Comment.new] do |f| %>
Body: <%= f.text_area :body, rows: 5, cols: 20 %>
<%= f.submit "Submit" %>
<% end %>
<hr/>
<h1>Comments:</h1>
<%= turbo_stream_from post %>
<%= turbo_frame_tag post do %>
<div id="<%= dom_id(post) %>">
<% post.comments.includes(:user).each do |comment| %>
<%= render comment %>
<% end %>
</div>
<% end %>
In Comment#create action I have:
post = Post.find(params[:post_id])
#comment = post.comments.new(comment_params)
#comment.user_id = current_user.id
respond_to do |format|
if #comment.save
format.turbo_stream { render turbo_stream: turbo_stream.prepend("post_#{post.id}", partial: "comments/comment", locals: { comment: #comment }) }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
In Comment model I have:
after_create_commit {
broadcast_prepend_to(target: "post_#{self.post.id}")
}
And in terminal I can see
[ActionCable] Broadcasting to : "<turbo-stream action=\"prepend\" target=\"post_6\"><template> <div>\n <p>\n <strong>Title:</strong>\n adsd\n </p>\n\n <p>\n <strong>Content:</strong>\n Asda\n </p>\n\n <p>\n <strong>User:</strong>\n Super Saiyan Vegeta\n </p>\n </div>\n\n <hr/>\...
Which is correct, but the comments list in a specific post still not updating, and I do see I have id="post_6" in the view...can someone tell me why? Thanks.
Try to change
<div id="<%= dom_id(post) %>">
to
<div id="post_<%= post.id %>">
And use such method in controller
if #comment.save
#comment.broadcast_prepend_to to post, target: "post_#{post.id}", partial: "comments/comment", locals: { comment: #comment }
end
instead of broadcast from model

Rails Remote True Ajax ActionController::UnknownFormat

I have a mini-update method in my model that only updates one field, set up in a chart (so I see the form for each row). It works properly, but want it to update using AJAX to be able to do multiple updates quickly without waiting for a full page reload.
The cell looks like this:
<td style="min-width: 150px" id="goody_cell<%= blog.id %>">
<%= render partial: "admin/goody_cell", locals: { blog: blog } %>
</td>
With this as the partial:
<% if blog.variation_id %>
<%= Variation.find(blog.variation_id).name %>
<% end %>
<%= simple_form_for(blog, url: update_goody_path(blog), remote: true, method: :patch, authenticity_token: true) do |f| %>
<% variation_collection = [] %>
<% Variation.all.each do |lm| %>
<% variation_collection << ["#{lm.name}", "#{lm.id}"] %>
<% end %>
<div class="row text-left">
<div class="form-group col-12 mb-0 pb-0">
<%= f.input :variation_id, label: false, prompt: "Choose a Related Goody", input_html: { id: "lmFor" + blog.id.to_s, class: 'browser-default', onchange: "this.form.submit();" }, collection: variation_collection, required: false %>
</div>
</div> <!-- row -->
<% end %>
My blogs/update_goody.js.erb looks like this:
$('#goody_cell<%= blog.id %>').html('<%= j render partial: "admin/goody_cell", locals: { blog: #blog } %>');
And my controller method is like this:
def update_goody
#blog = Blog.friendly.find(params[:id])
if #blog.update!(blog_params)
respond_to do |format|
format.js
end
else
format.html { render :edit }
format.json { render json: #blog.errors, status: :unprocessable_entity }
end
end
I have the route like this:
patch "blogs/:id/update_goody" => "blogs#update_goody", as: "update_goody"
When I try to update it, it does update the value, but before it renders the partial, I get an error saying:
ActionController::UnknownFormat - ActionController::UnknownFormat:
app/controllers/blogs_controller.rb:178:in `update_goody'
I've looked at SO posts like this but they all say that the error was fixed by adding remote: true, which I already have.
Can anyone help me get this working?
instead of remote: true, use local: false (Rails 6)
make sure you have both update_goody.html.erb and update_goody.js.erb in your views/blogs folder.
you dont have to add anything to update_goody.html.erb as its not rendering at all.
in your controller
def update_goody
#blog = Blog.friendly.find(params[:id])
if #blog.update!(blog_params)
respond_to do |format|
format.html
format.js
end
else
format.html { render :edit }
format.json { render json: #blog.errors, status: :unprocessable_entity }
end
end
note that format.html in respond_to block. it dosent matter you add it or not as you are set remote: true in your form
refer this
In my case i had to put redirect_to to render html
respond_to do |format|
format.js
format.html { redirect_to update_goody_path }
end

How to make ajax call and show error messages

Two of the action of My registration controller is new and create.
def new
#regist = Regist.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #regist }
end
end
def create
#regist = Regist.new(regist_params)
respond_to do |format|
if #regist.save
format.html { redirect_to #regist, notice: 'Regist was successfully created.' }
format.json { render json: #regist, status: :created, location: #regist }
else
format.html { render action: "new" }
format.json { render json: #regist.errors, status: :unprocessable_entity }
end
end
end
And the new form contain following code.
<%= form_for(#regist) do |f| %>
<% if #regist.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#regist.errors.count, "error") %> prohibited this regist from being saved:</h2>
<ul>
<% #regist.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.collection_select :student_id, Student.all, :id, :name %><br />
</div>
<div class="field">
<%= f.collection_select :semester_id, Semester.all, :id, :name %><br />
</div>
<div class="field">
<% for subject in Subject.find(:all) %>
<%= check_box_tag "regist[subject_ids][]", subject.id %>
<%= subject.name %><br>
<% end %>
</div>
<div class="field">
<%= f.label :date_of_birth %><br />
<%= f.text_field :date_of_birth %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Now, when someone click on submit button I want to make ajax call using remote true if there are validation errors and show the errors without reloading the page. And if there are no validation errors I want the user to be redirected to show page. How can I do this?
First of all you should add a remote: true to your existing form to allow remote action.
TODO this just add on the first line of your form the remote: true,
<%= form_for(#regist, remote: true) do |f| %>
the rest leave it as it is. Then you need to make your controller to respond to remote calls, therefore you need to alter the responds_to block of create action:
respond_to do |format|
if #regist.save
format.html { redirect_to #regist, notice: 'Regist was successfully created.' }
format.json { render json: #regist, status: :created, location: #regist }
format.js { render js: "window.location.href='"+regists_path+"'"}
else
format.html { render action: "new" }
format.json { render json: #regist.errors, status: :unprocessable_entity }
format.js
end
end
The last step you have to do is to add a file to your app/views/regists/ directory
where you should add a create.js.erb file:
<% if #regist.errors.any? %>
$('#new_regist').effect('highlight', { color: '#FF0000'}, 1000); // for highlighting
// or add here whatever jquery response you want to have to your views.
<% end %>
You will get your validation errors displayed like before above the form.
You have to add the redirect to your controller to the desirable action of your choice. I have added for you a window.location.href as a response to the regists_path.

view incorrectly reloads after uniqueness validation error

After you try to submit a new guideline and you see an error message on the form (due to the guidelines correctly failing validation)...the list of #specialties does not reload correctly (ie. it just says yes/no rather than the proper list you could see before you submitted with an error). I can't work out which part is wrong here...
VIEWS _form.html.erb
<%= simple_form_for(#guideline, html: {class: "form-horizontal"}) do |f| %>
<% if #guideline.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#guideline.errors.count, "error") %> prohibited this guideline from being saved:</h2>
<ul>
<% #guideline.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.input :title, label: 'Title (e.g. Asthma for under 12 months)' %>
<%= f.input :specialty, as: :select, collection: #specialties %>
<%= f.input :hospital %>
<%= f.input :content, as: :string, label: 'Link' %>
<div class="form-actions">
<%= f.button :submit, :class => "btn btn-info btn-large" %>
</div>
<% end %>
guidelines_controller.rb
def new
#guideline = Guideline.new
#specialties = Guideline.order(:specialty).uniq.pluck(:specialty)
respond_to do |format|
format.html # new.html.erb
format.json { render json: #guideline }
end
end
def create
#guideline = current_user.guidelines.new(params[:guideline])
respond_to do |format|
if #guideline.save
format.html { redirect_to #guideline, notice: 'Guideline was successfully created.' }
format.json { render json: #guideline, status: :created, location: #guideline }
else
#specialties = Guideline.order(:specialty).uniq.pluck(:specialty)
format.html { render action: "new" }
format.json { render json: #guideline.errors, status: :unprocessable_entity }
end
end
end
this is a common error. in your create action you should declare #specialties if the validation fails since that is needed in the new template.
def create
#guideline = Guideline.new params[:guideline]
if #guideline.save
else
# you need to declare #specialties here since it is needed in the new template
# which you want to render
#specialties = Specialty.all
render :new
end
end

How to use partials with more than one collection in rails

I want to use two collection #ad_item and #user in my partial. Here is my index erb...
<% if #ad_items.any? && #user.any? %>
<%= render partial: 'yourads/ad_item', collection: {#ad_items,#user} %>
<% end %>
and here is my controller ...
def index
#ad_items = Yourad.all
#user = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #yourads }
end
end
and here is my _ad_item.erb.html partial
<span class="date"><%= ad_item.created_at.to_date() %></span>
<span class="location"><%= ad_item.title %></span>
<div style="float:left"><%= ad_pic #user,ad_item %></div>
<div class="description"><%= ad_item.description %></div>
My helper function is ..
def ad_pic(user,ad)
cl_image_tag("Ad#{user.id}#{ad.id}.jpg", :version => rand(1000000000), :alt => "Ad pic",:width => 70, :height => 70, :crop => :fill)
end
it gives syntax error in ..
<%= render partial: 'yourads/ad_item', collection: {#ad_items,#user} %>
<%= render partial: 'yourads/ad_item', collection: #ad_items ,
locals: {users: #users} %>
and in view
<% for user in users %>
<span class="date"><%= ad_item.created_at.to_date() %></span>
<span class="location"><%= ad_item.title %></span>
<div style="float:left"><%= ad_pic user,ad_item %></div>
<div class="description"><%= ad_item.description %></div>
<% end %>
see Passing Local Variables
<%= render partial: 'yourads/ad_item', :locals => {:ad_items=>#ad_items,:user => #user }%>
you can access #ad_items as ad_items and #user as user in your yourads/ad_item partial

Resources