Rails flash message - ruby-on-rails

I am building an application on Rails where a user creates a Test, goes to the show view for that test and fills in a form with an answer to a question. If the answer to the question matches the "correct_answer" (defined in the controller), there will be flash message that the answer was correct and a button to continue to the root_path will appear. If the answer is wrong, the flash says "Wrong answer."
My problem is that the flash message will say wrong answer even if no answer has been given. I ONLY want that message to appear AFTER the user has submitted the form. I understand why this is happening, I just am unsure about how to fix it. Here is the show view for a test:
<div class="col-md-8 col-md-push-2">
<h4>Current Score: <%= #test.score %></h4>
<br /><br />
<div class="form_group">
<%= form_tag test_path(#test), :method=> 'get' do %>
<h4>What is my first name?</h4>
<div class="form-group">
<%= text_field_tag :answer, params[:answer], class: 'form-control' %>
</div>
<% if !flash[:success] %>
<div class="form-group">
<%= submit_tag "Submit", class: "btn btn-primary" %>
</div>
<% end %>
<% end %>
</div>
<% if flash[:success] %>
<%= link_to "Continue", root_path, class: "btn btn-success pull-right" %>
<% end %>
</div>
Here is the controller for tests, which contains the offending show action:
class TestsController < ApplicationController
def index
#test = Test.new
#tests = Test.all
end
def show
#test = Test.find(params[:id])
correct_answer = "jack"
user_answer = params[:answer]
if user_answer == correct_answer
flash.now[:success] = "That is correct!"
new_score = #test.score += 1
#test.update(score: new_score)
elsif params[:answer] != correct_answer
flash.now[:danger] = "Wrong answer"
end
end
def create
#test = Test.create(test_params)
if #test.save
redirect_to test_path(#test)
flash[:success] = "Test created"
else
flash[:danger] = "There was a problem"
render "index"
end
end
def destroy
#test = Test.find(params[:id])
if #test.destroy
flash[:success] = "Your test was removed"
redirect_to root_path
end
end
private
def test_params
params.require(:test).permit(:score, :user_id)
end
end
Is there a better way to do this? If not, can I somehow stop the flash message from appearing on the initial load? I ONLY want it to appear once the form has been submitted. Thanks in advance.

So your flash is triggered because params[:answer] is undefined and not == to jack.
You say:
My problem is that the flash message will say wrong answer even if no answer has been given.
But your logic is off for this outcome, your code says that params[:answer] is defined, which it can't be since the form is not rendered yet.
Maybe your condition should be:
elsif params[:answer].present? && params[:answer] != correct_answer
flash.now[:danger] = "Wrong answer"
end
Traditionally, the showing of the form and POST to a form are separete actions, which is why you're seeing this flash before anything even happens.

Related

Allow user to complete form with a delay, error : undefined method `errors' for nil:NilClass

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?

No route matches {:action=>"edit", :controller=>"quiz_bs", :id=>nil} missing required keys: [:id]

I'm trying to link to add an Edit Quiz link in my rails app, but am getting this error:
No route matches {:action=>"edit", :controller=>"quiz_bs", :id=>nil} missing required keys: [:id]
I have looked at similar posts (like this one) but their answers don't seem to solve the problem, even though they present similar situations.
The error is appearing for this line of my application.html.erb code:
<li>
<% if #user.quiz_bs == nil %>
<%= link_to "Body Structure Quiz", quiz_bs_path %>
<% else %>
<%= link_to "Body Structure Quiz ✓", edit_quiz_b_path(id: #user.quiz_bs) %>
<% end %>
</li>
but the link will also be on my show users page:
<h4>Body Structure</h4>
<% if #user.quiz_bs == nil %>
<p><%= link_to "Test Your Body Structure", new_quiz_b_path %></p>
<% else %>
<h3><%= #user.quiz_bs.bscode %></h3>
<p><%= link_to "Retest Results", edit_quiz_b_path(id: #quiz_bs.id) %></p>
<% end %>
Here is my quiz_bs_controller:
class QuizBsController < ApplicationController
before_action :require_sign_in
def show
#quiz_bs = QuizBs.find(params[:id])
end
def new
#quiz_bs = current_user.quiz_bs || current_user.build_quiz_bs
end
def create
#quiz_bs = QuizBs.new
#quiz_bs.bs01 = params[:quiz_bs][:bs01]
#quiz_bs.bs02 = params[:quiz_bs][:bs02]
#quiz_bs.bs03 = params[:quiz_bs][:bs03]
#quiz_bs.bs04 = params[:quiz_bs][:bs04]
#quiz_bs.bs05 = params[:quiz_bs][:bs05]
#quiz_bs.bs06 = params[:quiz_bs][:bs06]
#quiz_bs.user = current_user
if #quiz_bs.save
flash[:notice] = "Quiz results saved successfully."
redirect_to user_path(current_user)
else
flash[:alert] = "Sorry, your quiz results failed to save."
redirect_to welcome_index_path
end
end
def edit
#quiz_bs = QuizBs.find(params[:id])
#quiz_bs.assign_attributes(quiz_bs_params)
if #quiz_bs.save
flash[:notice] = "Post was updated successfully."
redirect_to user_path(current_user)
else
flash.now[:alert] = "There was an error saving the post. Please try again."
redirect_to welcome_index_path
end
end
def update
#quiz_bs = QuizBs.find(params[:id])
#quiz_bs.assign_attributes(quiz_bs_params)
if #quiz_bs.save
flash[:notice] = "Post was updated successfully."
redirect_to user_path(current_user)
else
flash.now[:alert] = "There was an error saving the post. Please try again."
redirect_to welcome_index_path
end
end
private
def quiz_bs_params
params.permit(:bs01, :bs02, :bs03, :bs04, :bs05, :bs06)
end
end
And the corresponding routes are:
quiz_bs GET /quiz_bs(.:format) quiz_bs#index
POST /quiz_bs(.:format) quiz_bs#create
new_quiz_b GET /quiz_bs/new(.:format) quiz_bs#new
edit_quiz_b GET /quiz_bs/:id/edit(.:format) quiz_bs#edit
quiz_b GET /quiz_bs/:id(.:format) quiz_bs#show
PATCH /quiz_bs/:id(.:format) quiz_bs#update
PUT /quiz_bs/:id(.:format) quiz_bs#update
I would be so grateful for any help figuring out the problem here!
In application.html.erb, change to:
<li>
<% if current_user.quiz_bs.nil? %>
<%= link_to "Body Structure Quiz", quiz_bs_path %>
<% else %>
<%= link_to "Body Structure Quiz ✓", edit_quiz_b_path(current_user.quiz_bs) %>
<% end %>
</li>
We want to change #user to current_user, as this view is a template for the entire website. There is no guarantee that #user will be a global variable in each and every controller (and that's probably not what you want here anyway). Using the current_user method will always be available, and get you the logged in user.
Note: the id: here is not necessary, the model passed will be used as the id.
In users/show.html.erb, change to:
<h4>Body Structure</h4>
<% if #user.quiz_bs == nil %>
<p><%= link_to "Test Your Body Structure", new_quiz_b_path %></p>
<% else %>
<h3><%= #user.quiz_bs.bscode %></h3>
<p><%= link_to "Retest Results", edit_quiz_b_path(#user.quiz_bs) %></p>
<% end %>
#quiz_b is a variable that you would not have defined in your users_controller like you do in the quiz_bs_controller. You are accessing #user.quiz_bs, so you want to link to that particular instance in your code.
The global #quiz_b only works on pages where you have defined this variable, which would be any page under the quiz_b routes.
Finally, in quiz_bs_controller:
def quiz_bs_params
params.require(:quiz_bs).permit(:bs01, :bs02, :bs03, :bs04, :bs05, :bs06)
end
When using this method in conjunction with a form declared as:
<%= form_for #quiz_bs do |f| %>
You need to require :quiz_bs from your params in order to scope your parameters to the model being edited in the form; in this case, #quiz_bs. Otherwise, no variables will be found when you do update/create the quiz.
Here is your problem:
No route matches {:action=>"edit", :controller=>"quiz_bs", :id=>nil} missing required keys: [:id]
You have to pass the Edit action an ID, it's part of the routing structure. In your routes output it even shows you this:
/quiz_bs/:id/edit
Without that id, the /:id/edit route is simply not going to work -- that is a Rails mechanism.
In your Gemfile:
group :development do
gem 'pry-rails'
end
And:
bundle install
Then in your application.html.erb above that logic do:
<% binding.pry %>
<% if #user.quiz_bs == nil %>
<p><%= link_to "Test Your Body Structure", new_quiz_b_path %></p>
<% else %>
<h3><%= #user.quiz_bs.bscode %></h3>
<p><%= link_to "Retest Results", edit_quiz_b_path(id: #quiz_bs.id) %></p>
<% end %>
Now hit the page again. Pry will cause a debug breakpoint that will allow you to evaluate the #user object in the terminal where your server is currently running. My best advice to you is to learn to debug your own code -- this is a situation with a lot of context you can't quite provide without providing access to your app.

Create method wont Create New Relationship in databse even though success flash appears

I'm trying to create a relationship in the database, but cant get it to work.
why wont the create action work? why can't it find the if statement? I think i havent written the create action correctly in the relationships controller but don't know how to fix it
When a user clicks 'add relationship'. the app should create the new relationship.
At the moment a user clicks 'add relationship' and this flash success msg appears:
You are now connected !
this is the information that comes across to the view where the flash success msg appears:
relationships.html.erb
relationship: !ruby/hash:ActionController::Parameters
followed_id: '3'
commit: Add Relationship
action: create
controller: relationships
but! a relationship is not created in the database
Here is the flow users take with comments to help explain:
users/index.html.erb:
# 1. USER SEARCHES FOR ANOTHER USER IN SYSTEM:
<%= form_tag users_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
# 2. IF ANOTHER USER IS FOUND, THEIR NAME IS PRESENTED AND AN 'ADD RELATIONSHIP' BUTTON APPEARS:
<ul>
<% #users.each do |user| %>
<li>
<%= user.name %>
<%= render 'followed', followed: user %>
</li>
<% end %>
</ul>
_followed.html.erb:
# 3. ADD RELATIONSHIP BUTTON INFORMATION. THE USER CLICKS THE BUTTON, RELATIONSHIPS/CREATE IS THE NEXT PAGE. NO RELATIONSHIP IS CREATED
<%= form_for :relationship, url: relationships_path, method: :post do |f| %>
<div class="form form-actions">
<%= f.hidden_field :followed_id, value: followed.id %>
<%= f.submit "Add Relationship", class: "btn btn-primary" %>
</div>
<% end %>
relationships_controller:
def create
if params[:relationship] && params[:relationship].has_key?(:followed_id)
#followed = User.find_by(name: params[:active_relationship][:followed_id])
#active_relationship = current_user.active_relationships.new(followed: #followed)
#active_relationship.save
flash[:success] = "You are now connected !"
else
flash[:danger] = "Relationship required"
end
end
Looks like the error is in the second line in your create controller. In your if statement you have params[:relatioship] so I guess you would write
#followed = User.find_by(name: params[:relationship][:followed_id])
and I guess the followed_id shouldn't be a name
You should refactor the code like this:
if params[:relationship]
#followed = User.find(params[:relationship][:followed_id])
#active_relationship = current_user.active_relationships.new(followed: #followed)
if #active_relationship.save
flash[:success] = "You are now connected !"
else
flash[:danger] = "Relationship required"
end
else
flash[:danger] = "Something was wrong!!"
end
You might also use pry to debug codes. Links for reference http://pryrepl.org

Rails validating with two models

Ok, bit confused on how to solve this issue.
I have one form and two models. Here is my form:
<% if #booking.errors.any? %>
<% #booking.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<%= form_for :booking, url: bookings_path do |f| %>
<%= label_tag :email, "Guest's Email Address" %>
<%= text_field_tag :email %>
<%= f.label :nights, "Nights" %>
<%= f.text_field :nights %>
<%= f.label :nights, "People" %>
<%= f.text_field :people %>
<%= f.label :nights, "Arrival Date" %>
<%= f.text_field :arrival %>
<% end %>
As you can see, the email field isn't part of the form builder. The email address will be used to create a new Guest record if the email doesn't already exist. Once I have the ID of the guest then the booking record can be made also.
Here is the code for create action in my BookingController - where the form is submitted to...
...
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
render 'new'
end
end
...
I do realise this question isn't new but I can't find a good solution anywhere to my problem - I want to be able to set the form up properly (if necessary) and validate all fields using the two models. Then I need to display the error messages. At the moment, my email is ignored during validation and I'm not sure what to do next.
Any help much appreciated.
It seems to me that the easiest way to is to validate the email in the controller itself and add any validation error to the booking variable. Something like this:
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
<% if params[:email].blank> %>
#booking.errors.add(:email, "can't be blank.")
<% end %>
#You can do the same thing for whatever other validation errors you have
render 'new'
end
end
Note: I did not test the code
This is probably not the best way possible but it gets the job done and is easy. You could use accept_nested_attributes_for but it seems to me a little bit unnecessary considering that you are only validating an email. Nevertheless, if you want to do it the cleanest way, stick with accept_nested_attributes_for.
EDIT
Actually, your code is the right track. You just made a syntax error. The real reason your guests errors are not being shown is that you used a local variable instead of a instance variable. Try this:
#guest = Guest.find_or_create_by(:email => params[:email])
Your error messages should be displayed with the code you already have
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
EDIT 2
In order to avoid a booking instance from beings saved in case the guest email is invalid you can do something like this:
if !#guest.errors.any? && #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
Therefore, if the guest has any errors, the if statement will terminate before the #booking.save statement is executed.
You can try to do a transaction. If one of them is invalid, rails will do a rollback and you can render the errors.
Follow this question code, see if it helps.

Welcome flash message after sign up?

When users sign up to my application I have a welcome message appear on the home page using Devise's sign_in_count column.
def home
if current_user.sign_in_count == 1
flash.now[:notice] = "Welcome!"
end
end
The only problem though is it stays there until they sign out and sign back in. How can I make it show only once and disappear when the page is refreshed or changed? Is there some rails way to do this?
Thank you.
EDIT
application.html.erb
<body>
<div class="container">
<%= render "shared/flash_message" %>
<%= yield %>
</div>
</body>
_flash_message.html.erb
<% [:notice, :error, :alert].each do |level| %>
<% unless flash[level].blank? %>
<div class="span12">
<div class="<%= flash_class(level) %> fade in">
×
<%= content_tag :p, flash[level] %>
</div>
</div>
<% end %>
<% end %>
In your layouts/application.html.erb you should have something like this:
<% flash.each do |key, value| %>
<%= content_tag(:div, value, class: "flash #{key}") %>
<% end %>
Doing this way, should works as you expect.
EDIT
What if you verify for the current_user is already set?
def home
if current_user && current_user.sign_in_count == 1
flash.now[:notice] = "Welcome!"
end
end
EDIT 2
OK! Got it! The sign_in_count column from Devise will remain the same until the next login, so, it will always keep showing you the Welcome! message. To make this work as you expect you have to create a flag on it.
def home
if current_user && current_user.sign_in_count == 1
unless session[:display_welcome]
flash.now[:notice] = "Welcome!"
session[:display_welcome] = true
end
end
end
You can try using session or cookies.
If the user logs in for the first time, change the logic to compare against 0. After setting flash message update the sign_in_count to 1,
def home
if current_user.sign_in_count == 0
flash.now[:notice] = "Welcome!"
current_user.update_attribute(:sign_in_count, 1)
end
end

Resources