Here is my create action. It creates a new instance of Message, which is checked with model validations.
Then there is a simple if else loop that sends the message if the model validations were fulfilled and renders the 'new' view if they want to send another. If the model validations weren't fulfilled, it simply renders the 'new' view again.
The flash hash is also populated with relevant information, a success message and an alert message if the message was successful or not.
def create
#message = Message.new(message_params)
if #message.valid?
Contactform.contact(#message.name, #message.town, #message.email, #message.content).deliver
flash[:success] = "Sent message"
render 'new'
else
flash[:alert] = "Alert"
render 'new'
end
end
However, I don't think I'm doing it right, because the messages stay in the flash. They aren't cleared after they've been viewed once.
How should I ensure that the flash is cleared, as it should be?
Update
This is how the error comes about. I send a message successfully, and I'm routed back to the 'new' view and I receive a 'success' flash. I attempt to send an erroneous message, and I'm routed back to the 'new' view and I receive both the 'success' flash from before, and a 'alert' flash. Very confusing for the user!
The second I browse to another page, both flashes disappear, but that's the problem I think. Something to do with coming back to the same page so Rails doesn't know to clear the flash. Can I force clear the flash?
I think reading the section about flash.now in the rails doc will help clear this up for you: http://guides.rubyonrails.org/action_controller_overview.html#flash-now.
Flash sticks around for the next request. In this case, you aren't issuing a new request after your create action - you are just re-rendering the new view. I think flash.now is what you want to be using here.
I would also say that in your create success case, the create action should be doing a redirect rather than just re-rendering the view. Are redirect_to and render exchangeable? explains the difference between redirecting and rendering
Related
I am trying a simple download from a server. This is using Rails 5.01, Ruby 2.24p230.
The view has a link to the controller to download data. It gets there just fine.
The controller method is just this:
def download
send_data("some sample text", filename:sample.txt)
flash[:success] = "it worked"
end
The result is that a file is named sample.txt containing the correct text is downloaded to the client. flash never happens. The view which linked to the controller is still on the screen, without any page refresh. A view called "download.html.erb" is never called.
My questions are:
Is there a simple way to cause some communication with the client following the send_data? It would be nice to tell the person on the client something after a successful download.
After the send_data, what should happen?
Thank you for taking the time to answer this.
To understand what is going on here you have to understand how the session and flashes work.
A flash message is stored in the session and carried on to the next request. When the request is finished flash messages from the previous request are removed from the "flash hash" which is stored in the session.
class TestController
# GET /foo
def foo
flash[:notice] = "Hello"
redirect_to '/bar'
end
# GET /bar
def bar
flash.now[:alert] = " world!"
end
end
So when the user requests /foo they are redirected to /bar and the flash hash will contain:
{
notice: "Hello",
alert: " world!"
}
So how is this relevant? When the client clicks the download button the flash message you set will be seen in the next request they perform. Which is not really that useful. Instead what you want to do is probably just use javascript and display a popup or some sort of message when the user clicks the download link.
When you send data to the client few will actually allow you to set any kind of redirect to be followed or will ignore any headers you send. This is because the huge number of potential annoying or malicious uses.
Very general question I was hoping someone could clarify for me. I'm looking at the basic generated scaffold code for a model called products. I noticed the new and show actions in the controller don't have much written in them. In fact, show is entirely empty and new only has the line "#product = Product.new". I know these 2 actions are supposed to go to a separate view. A view to show the resource, and a new form view to input info and create a resource, respectively.
So, I'm curious how that actually happens. Other actions have redirect_to :some_path which makes sense, but how exactly does "render action 'show', location: #product " bring up the items show page when the action is empty? Also how is that different from redirect_to #product ?
thanks
Will
Render produces a string that will displayed as the response to the request to the application.
redirect_to produces a response header resulting in a new request to the application.
The render action 'show', location: #product uses the the file app/views/products/show.html.erb with #product as a parameter to produce the html which will be returned.
The reason some of the controller functions are empty is that rails are using defaults. So if you don't tell rails what to render then rails will look for a file in the appropriate location.
Methods ending with redirect_to are usually post/patch requests saving something in your database and after the requested action has been performed they redirect the user to a method meant for displaying information.
A tutorial I am following has the below code which it mentions is not quite right because the error flash persists one request longer than desired because render doesn't count as a request. The solution is to use flash.now instead.
But how is it even possible for the error flash to persist one extra request? Given that Rails is stateless how is the information of the flash stored for the next request?
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
# Sign the user in and redirect to the user's show page.
else
flash[:error] = 'Invalid email/password combination' # Not quite right!
render 'new'
end
end
def destroy
end
end
Use flash.now instead of flash.
The flash variable is intended to be used before a redirect, and it persists on the resulting page for one request. This means that if we do not redirect, and instead simply render a page, the flash message will persist for two requests: it appears on the rendered page but is still waiting for a redirect (i.e., a second request), and thus the message will appear again if you click a link.
To avoid this weird behavior, when rendering rather than redirecting we use flash.now instead of flash.
The flash is stored in the user's Session, which is associated with them on subsequent requests using HTTP cookies. The flash is just one part of the session whose data is automatically flushed on the next request. See the Rails Guide to Action Controller for more details.
I'm not sure how to check flash messages with functional tests. My problem has something to do with redirect_to because my test for flash[:error] DOES pass. Here is what my create action looks like:
def create
#user = User.new(params[:user])
if #user.save
redirect_to #user, flash: { success: "Welcome!" }
else
flash.now[:error] = "Signup failed:"
render 'new'
end
end
I have a passing test for flash[:error] that 1) checks that flash[:error] is the correct value, 2) checks the page for the flash div, and 3) gets a new page and checks that the flash message has been cleared. I tried to make an analogous test for flash[:success] but it is failing when it tries to find the div on the page. Here is the failing test:
should "show flash[:success] message" do
post :create, user: { name: "valid user",
password: "userpw",
password_confirmation: "userpw",
email: "a#b.com" }
assert_redirected_to user_path(assigns(:user)), "user isn't being redirected to their page after registration"
assert_equal flash[:success], "Welcome!", "flash[:success] isn't being set properly"
assert_select "div.alert-success", flash[:success]
get :new
assert flash.empty?, "there shouldn't be any flash messages left after getting new page"
end
When I test this manually in a browser, it behaves as expected, but I'm not sure how to write the tests to reflect this. The first two assertions are passing (so the test recognizes that it is being redirected correctly, and that flash[:success] is being set to "Welcome!"). Based on that, I would think the assert_select statement would be able to find the div, but if fails with this error message:
Expected at least 1 element matching "div.alert-success", found 0.
So I tried adding puts #response.body right before the assert_select statement, which printed this:
<html><body>You are being redirected.</body></html>
This explains why it couldn't find the flash div, but I'm not sure how to "finish" the redirect so that it renders the correct view with the flash div. If I add a get statement right before the assert_select statement, the test passes, because now #response.body has the flash div. However, this doesn't seem like an effective test because it doesn't reflect the behavior of my application - the user doesn't have to make a new request to see the flash message.
It also doesn't guarantee that the flash message shows up on the correct page. If I use the statement get :new, the assertion still passes, which implies that the flash message would be present on the users#new view. Even if I use
get :show, id: assigns(:user).id
to make sure the flash message shows up on the correct page, this still feels wrong because 1) now it is a success response instead of a redirect response, and 2) it still doesn't explain why I have to manually request ANY page to find the flash div - according to the docs, "The flash is a special part of the session which is cleared with each request."
Anyway, is there a way to test that a flash message shows up correctly after a redirect?
I'm answering this question from the POV that what you're trying to test would be better tested elsewhere. If you're having a lot of trouble setting up the test, odds are there's a better way to test it :)
Testing that flash messages get cleared - flash messages are built into Rails, so it doesn't make a lot of sense to test something that the framework is handling for you.
Testing that elements show up on the page - view specs don't belong in controllers. Here is a description of the role of the controller:
A controller can thus be thought of as a middle man between models and
views. It makes the model data available to the view so it can display
that data to the user.
The controller is supposed to make data available to the view, not to specify how the data gets rendered. Imagine if you changed the name of your CSS class, then you'd have to also update your controller specs.
If you really want to test that flash messages show up, I would use an integration test.
I have a weird problem with flash messages not showing up in IE (tried 8 and 9):
it always works with other browser
the problem is only on one page (this page renders different forms based on a parameter)
the flash message always appears on development, but only sometimes on staging and prod
I see the flash message being logged in all cases, [notice] Your changes have been saved. Next step..., even when it doesn't appear on the page!
The error flash message always shows up, it's the notice that doesn't work properly.
Here's my update action:
def update
#form = Forms::Events::EditForm.build_for(#event, params[:event])
if #form.save
redirect_to edit_challenge_path(#form.event, form: #form.event_name), notice: "Your changes have been saved. #{#form.next_form}".html_safe
else
flash.now[:alert] = "Please correct the errors highlighted below."
render "groups/events/edit"
end
end
Any ideas on what could be wrong?
Agreeing with #AnthonyAlberto's comment. What you want is the .now method of flash, e.g. flash.now[:alert] = ... Here's a good explanation of the difference.