I am learning Ruby on Rails. Rails 7 + Turbo stream... I am following a tutorial and I am having a problem with displaying the Notice message.
Can you help me understand why I am not getting notice message after I create a new quote?
Here is a create action in QoutesController.rb. Only when I put this line of code:
flash[:notice] = "Gets displayed with this line and a refresh... "
before the format.html line, then I will get the notice message after I refresh the page upon creating a quote. Only then the message gets displayed.
Can you please help me understand this?
(Yes, I am using a Devise gem here, Turbo stream, Turbo Rails, Rails 7)
Thank you
def create
#quote = current_company.quotes.build(quote_params)
if #quote.save
respond_to do |format|
flash[:notice] = "Gets displayed with this line and a refresh... " #weird
format.html { redirect_to quotes_path, notice: "Quote was successfully created." }
format.turbo_stream
#debugger
end
else
render :new, status: :unprocessable_entity
end
end
In Rails 7, unrefresh page when you create successfully. So, flash[:notice] not display.
Create views/shared
In share folder, we have _flash.html.erb, _notices.html.erb
_flash.html.erb
<div class="alert <%= bootstrap_class_for(msg_type) %> alert-dismissible fade show" role="alert">
<div class="container text-center">
<%= message %>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
_notices.html.erb
<div id="flash">
<% flash.each do |msg_type, message| %>
<%= render partial: "shared/flash", locals: { msg_type: msg_type, message: message } %>
<% end %>
</div>
When you create success -> create views/qoutes/create.turbo_stream.erb
<%= turbo_stream.update "flash", partial: "shared/flash", locals: { msg_type: :notice, message: "your message" } %>
// msg_type: :notice, :alert, :error, :success
In views/layouts/application.html.erb
<%= render 'shared/notices' %>
<%= yield %>
In application_helper
module ApplicationHelper
def bootstrap_class_for(flash_type)
{
success: "alert-success",
error: "alert-danger",
alert: "alert-warning",
notice: "alert-info"
}.stringify_keys[flash_type.to_s] || flash_type.to_s
end
end
and if create failed
ex:
def create
if #object.save
// code
else
render_flash(:alert, full_messages(#post.errors.full_messages))
end
end
In application_controller
class ApplicationController < ActionController::Base
protected
def render_flash type, message
render turbo_stream: turbo_stream.update("flash", partial: "shared/flash", locals: { msg_type: type, message: message })
end
def full_messages messages
messages.join("\n")
end
end
=> This is my way. Hope to help you.
Related
I try to allow user to fill the form with a delay between each user,
The delay is set to 100 between the last user registered in my database and the new one
Here is what I do in my controller :
class EmailsController < ApplicationController
def index
redirect_to root_path
end
def new
#email = Email.new
end
def create
if Time.now - Email.last.created_at < 100
respond_to do |format|
format.html{render :new, notice: "Wait !"}
end
else
respond_to do |format|
#email = Email.create(email_params)
if#email.persisted?
format.html {redirect_to invoice_index_path, notice: 'Email validated '}
else
format.html{render :new}
end
end
end
end
private
def email_params
params.require(:email).permit(:email, :id_user)
end
end
My view :
<div class="container">
<% if flash[:notice].present?%>
<center><p id="notice" class="alert alert-success"><%= flash[:notice] %></p></center>
<%end%>
<center><h1>Nouvelle Connection </h1></center>
<%= form_with model: #email, local: true do |form|%>
<% if #email.errors.any? %>
<div id="error explanation" class="alert alert-danger">
<p>Erreur(s) : </p>
<ul>
<% #email.errors.full_messages.each do |message|%>
<li><%=message%></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= label_tag :email %> :
<%= form.email_field :email, placeholder: "Insérez votre email", class: "form-control" %>
</div>
<div class="form-group">
<%= form.submit class: "btn btn-primary btn-lg btn-block", value: "Se connecter" %>
</div>
<%end%>
</div>
When I fill the form, nothing happen, just the url change from http://localhost:3000/ to http://localhost:3000/emails and when I re-click on Validate, it goes to http://localhost:3000/emails/53 with the error The action 'update' could not be found for EmailsController but I guess I have this error because I don't have any update/edit in my controller.
Do you know how to fix that to allow me a delay between each user ?
Edit : I now have the error undefined method errors for nil:NilClass in my view at the line <% if #email.errors.any? %>. I understand this is because my #email is nil but I tried to integrate this in my view :
<% if #email.nil?%>
<%= Wait for the moment %>
<%end%>
Also tried : <% if #email&.errors&.any? %> why so, I return to the URL : http://localhost:3000/emails
But still have the same error, do you know how to fix it ?
First off, in your view you check for notice.present? and that should be flash[:notice].present?. So then the error will be shown.
Secondly, in your controller you are actually first creating the email, and only then checking if it should be created, and then not even removing it? So the only result is that people will see a notice, but the email would still be created.
So instead do something like:
def create
if Time.now - Email.last.created_at < 100
respond_to do |format|
#email = Email.new(email_params)
format.html do
flash[:notice] = 'Wait! You cannot enter emails so quickly!'
render :new
end
end
else
respond_to do |format|
#email = Email.create(email_params)
if #email.persisted?
format.html {redirect_to invoice_index_path, notice: 'Email validé '}
else
format.html{render :new}
end
end
end
end
Secondly this does seem to mean that any user wanting to enter a new email, will be punished if another user has also entered another email. So in that case you might want to check as follows:
Email.where(created_by_id: current_user.id).last
to only check for Email 's the current user has entered (but maybe that is taking your problem/solution too far?).
Also note there are much better/efficient ways to throttle requests (rate limiting) using apache/nginx (which is probably how it will be deployed), so is this actually a good approach to handle in your ruby on rails code?
I have created rails 5 application and I have the following code in controller and views for showing flash messages. I saw many question like this but I could not solve the problem.
controller side
respond_to do |format|
if #trigger.update(trigger_params)
flash[:success] = 'Trigger was successfully updated.'
format.html { redirect_to edit_project_trigger_path(#trigger, project_secret_key: #project.secret_key) }
format.json { render :show, status: :ok, location: #trigger }
else
error = ''
#trigger.errors.full_messages.each do |msg|
error += "#{msg} <br>"
end
flash.now[:error] = error
format.html { render :new }
format.json { render json: #trigger.errors, status: :unprocessable_entity }
end
end
view side
<% if flash[:error].present?%>
<div class="alert alert-danger alert-message alert-message-common" role="alert">
<%= flash[:error].html_safe %>
</div>
<% end %>
<% if flash[:notice].present? %>
<div class="alert alert-success alert-message alert-message-common" role="alert">
<%= flash[:notice].html_safe %>
</div>
<% end %>
<% if flash[:alert].present? %>
<div class="alert alert-danger alert-message alert-message-common" role="alert">
<%= flash[:alert].html_safe %>
</div>
<% end %>
<% if flash[:success].present?%>
<div class="alert alert-success alert-message alert-message-common" role="alert">
<%= flash[:success].html_safe %>
</div>
<% end %>
It's show when using alert but success messages are not shown. Is there any problem in rails 5 or any problem in my code ?
I hope alert working because of page render and success not working because of redirect.
Only alert and notice flash keys are supported by default.
To add success:
class ApplicationController
add_flash_types :successs
Reference: Ruby Guides
try this:
format.html { redirect_to edit_project_trigger_path(#trigger, project_secret_key: #project.secret_key), :success => 'Trigger was successfully updated.' }
This should work:
format.html { redirect_to edit_project_trigger_path(#trigger, project_secret_key: #project.secret_key), flash: {success: 'Trigger was successfully updated.'} }
For some reasons, in rails you should you either notice or alert keys in redirect_to method, but if you need success or something else, you should use nested hash in flash key.
Check here http://blog.remarkablelabs.com/2012/12/register-your-own-flash-types-rails-4-countdown-to-2013
The Rails docs for redirect_to mention that "There are two special accessors for the commonly used flash names alert and notice as well as a general purpose flash bucket." You need to use regular flash syntax for any others.
I have a model Snippit and I want users to be able to delete a snippit, from a list, and then show an alert saying it was deleted, all using ajax. I have figured out how to do the actual deleting, but not the alert.
Here's the code:
snippits_controller.rb
def destroy
#snippit = Snippit.find(params[:id])
#snippit.destroy
respond_to do |format|
format.html { redirect_to snippits_url}
format.json { head :no_content }
format.js {render :alert => "Sippit destroyed. "}
end
end
destroy.js.erb
$('#snippit_<%= #snippit.id %>').remove();
index.html.erb
<% #snippits.each do |snippit| %>
<span class="panel panel-default" id="snippit_<%= snippit.id %>">
<%= link_to edit_snippit_path(snippit), :class => "text" do %>
<div class="panel-body">
<h3 class="text"><%=snippit.title%></h3>
<p class="trash"><%= link_to snippit, method: :delete, remote: true do %>
<i class="fa fa-2x fa-trash-o"></i>
<% end %></p>
</div>
<% end %>
</span>
<% end %>
Any and all help is greatly appreciated :)
If you want a JS alert response, then you'd want something like the following instead
format.js {render js: "alert('Sippit destroyed.');"}
The format.js render above means you're rendering a JS response. Your alert render :alert => "Sippit destroyed. " only works for HTML response because the flash[:alert] is rendered in the HTML page, but since you are rendering a JS response, then you'd either do the JS alert implementation above OR you partially update the HTML page to update the flash message by something like the following
destroy.js.erb
$('#snippit_<%= #snippit.id %>').remove();
$('#flash_container').html('<%= j render partial: "flash_container" %>');
UPDATE (Added working controller code for method 2: using destroy.js.erb above)
def destroy
#snippit = Snippit.find(params[:id])
#snippit.destroy
respond_to do |format|
format.html { redirect_to snippits_url}
format.json { head :no_content }
format.js { flash.now[:alert] = #snippit.destroyed? ? 'Sippit destroyed.' : #snippit.errors.full_messages }
end
end
I added a failure-handler code above for format.js. It will set the flash alert message into either 'Sippit destroyed' if #snippit was successfully destroyed, OR into 'Some failure to destroy error' if #snippit was not destroyed.
Currently, this is the code I have for creating a new message:
if #message.save
respond_to do |format|
format.html { redirect_to messages_path }
format.js
end
else
flash[:notice] = "Message cannot be blank!"
redirect_to :back
end
How do I get the same message to print in Ajax? Would also like control to format it and position it.
In aplication controller
after_filter :add_flash_to_header
def add_flash_to_header
# only run this in case it's an Ajax request.
return unless request.xhr?
# add different flashes to header
response.headers['X-Flash-Error'] = flash[:error] unless flash[:error].blank?
response.headers['X-Flash-Warning'] = flash[:warning] unless flash[:warning].blank?
response.headers['X-Flash-Notice'] = flash[:notice] unless flash[:notice].blank?
response.headers['X-Flash-Message'] = flash[:message] unless flash[:message].blank?
# make sure flash does not appear on the next page
flash.discard
end
Move notifications code into partial:
<div class="noticesWrapper">
<% flash.each do |name, msg| %>
<div class="alert alert-<%= name == :notice ? "success" : "error" %>">
<a class="close" data-dismiss="alert"><i class="icon-remove"></i></a>
<%= msg %>
</div>
<% end %>
</div>
In js.erb file:
$('.noticesWrapper').html("<%= j(render partial: 'layouts/flash_notices') %>");
In controller action you need to flash messages with flash.now:
flash.now[:error] = "your message"
I cannot figure out why my rails views are not recognizing flash[:notice] or flash[:error]. I keep getting the following error regarding the partial view being rendered. The specific error is:
ActionView::Template::Error (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]):
In my controller I have
def index
#organisms = Organism.all
flash[:error] = "test"
flash[:notice] = "test"
respond_to do |format|
format.html
format.json { render :json => #organisms }
end
end
In my index.html.erb file I render out a partial through:
<%= render "shared/flash" %>
The partial has the following code.
<div id="flashes">
<% if flash[:notice] %>
<p id="flash_notice" class="messages notice"><%= flash[:notice] %></p>
<%= javascript_tag "$('#flash_notice').effect('highlight',{},1000);" %>
<% end %>
<% if flash[:error] || flash[:errors] %>
<p id="flash_errors" class="messages errors"><%= flash[:error] || flash[:errors] %></p>
<%= javascript_tag "$('#flash_errors').effect('highlight',{},1000);" %>
<% end %>
<% flash[:error] = flash[:errors] = flash[:notice] = nil %>
</div>
However, if instead of rendering the partial I throw in <%= notice %> it renders out the notice.
If I take the partial code and stick it in the top of the index.html.erb file it renders correctly. Thus, I assume that I am rendering the partial view wrongly?
Any help is much appreciated. Thanks!
Don't name your partial flash. Ruby on Rails creates a local variable with the same name as the partial. In your case, a flash local variable is being created.
Rename your partial to something other than flash and it should work.
Also, you shouldn't need to set flash to nil at the bottom of your partial. Let Rails take care of that for you.
You have to pass the flash to the partial:
<%= render 'shared/flash', flash: flash %>
Or a bit longer:
<%= render partial: 'shared/flash', locals: { flash: flash } %>