I’m having an issue in Rails 3 where the flash hash seems to be returning things one request too early. That is, it seems to return things upon rendering that were set in that very same request. For example, consider a controller action that does:
add_warning "Danger, will robinson."
In my ApplicationController, I have:
before_filter :set_errors
#...
def set_errors
flash[:errors] ||= []
flash[:warnings] ||= []
flash[:notices] ||= []
end
#...
def add_warning(msg)
flash[:warnings] << msg
end
And my application.html.erb layout template has
<% flash[:warnings].each do |msg| %>
<div class="warnings"><%= msg %></div>
<% end %>
Based on what I’m understanding from the Rails guide, the flash contents shouldn’t be rendered in this same request unless I’m using flash.now. And, if I have a redirect_to, they SHOULD be rendered in that second request. But they don’t show up at all when the redirect_to happens.
It turns out that the reason for the issue was the set_errors method, combined with my lack of understanding of how the flash hash works.
It seems that if a new value is not assigned to a key in flash, then the value of that key will be nil on the NEXT request. This may seem obvious, but the implications are subtle because I was assigning values to keys with ||= (“or-equals”) on each request.
Consider the case where I have a series of requests to the same action, which don’t ever call add_warning:
On the first request, flash is an empty hash. It contains no keys, so the ||= assigns some.
On the second request, flash is a hash with three keys, each value an empty array. Now, since each key has a value, the ||= does not assign anything. Which means that on the third request, rails has cleared those values and flash is an empty hash once again.
Now, consider this case:
On the first request, flash in an empty hash. It contains no keys, so the ||= assigns some.
On the second request, flash is a hash with three keys, each value an empty array. Now, an action calls add_error. However, an array already exists at flash[:warnings]. So, a value is pushed into that array. The contents of the array have changed, but the value of flash[:warnings] has not changed – it is still a reference to the same array. Hence, two implications:
So, in conclusion, the subtle point is that assigning a new value to a key in the flash hash is what causes it to be available in the following request.
EDIT: I guess it might help to post my solution, which is to eliminate the set_error and before_filter entirely, and instead to put the ||= [] fragment in the add_error method. That way, values are assigned to the flash hash only when they should be – when an error message is added.