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.
flash.now need when you can't lost params[] for example: user filling the form for new post and have mistake in title for example (action new in Posts controller), he push submit and data send to create action, we have params[:post][:title] but if it's not valid and after we redirect to new action we already haven't params[:post][:title] and we can't fill field and user lost all filled data and user will be angry =) In this case, we use render and we still need to show warning, we use flash.now.
In other way we use just flash[] new action (1)-> create action (we set flash[] var) (2)-> index action we show flash[]. Like params in 1st example we lost flash[] after 2nd step.
Sorry for my pretty bad English, especially in long text.
flash and flash.now both render, but flash.now does so immediately, while plain old flash only renders after you redirect.
http://blog.vedanova.com/2010/09/29/rails-flash-now/
Based on your main question, it sounds like you want to use flash instead of flash.now
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:
The value of flash[:warnings] (i.e., the reference to an array) has not changed during this action, so because we've modified the same object that rails assigned to flash[:warnings] at the beginning of the request, it gets rendered with the newly pushed message at render time.
The value of flash[:warnings] (i.e., the reference to an array) has not changed during this action, so the action prompted by the next request will not have a :warnings key in flash at all.
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.
Related
I am an experienced PHP developer but new to RoR and trying to understand how every thing works. I know how to use flash hash i.e in my action method, I'll set
flash[:notice] = 'some message'
and in my view, i'll display that.
This mechanism is also implemented in Yii framework. I understand how it works there. The thing that I don't understand is how it actually works here in RoR. flash is just a local variable then how can I access it in my views?
flash is actually a method. It's not in your controller, but the Rails controller delegates it to the request object. So the flash method is defined in the request object, but you can access it from your controllers, and from the view.
Check the link to the ActionDispatch::Request code. The flash is actually stored inside the session when set. The next time the user requests a page, the flash is accessible for use in the views.
In your view you can just access it like this:
<%= flash[:notice] %>
Or in a aesthetically more pleasant way (this can only be done with notice and alert since they're so frequently used):
<%= flash.notice %>
See the documentation for more information.
'flash' is not a local variable. It is part of session like 'session' hash. What this implies is that session hashes (flash, session, cookies) are shared values between ActionController::Base and ActionView::Base. Therefore session hashes are accessible from controllers and from views.In order to access flash in views, just use it as you would use it in controller. Referring to your earlier code , you would print notice like this:
<% if flash[:notice] %>
<p><%= flash[:notice] %></p>
<% end %>
For further reference on this topic please checkout: guides
The flash hash is basically a variable which is populated with each controller/action request, and then is reset after the request has been performed. As Adilbiy Kanzitdinov has mentioned - it's set in the session hash :
The flash provides a way to pass temporary objects between actions.
Anything you place in the flash will be exposed to the very next
action and then cleared out. This is a great way of doing notices and
alerts, such as a create action that sets flash[:notice] = "Post
successfully created" before redirecting to a display action that can
then expose the flash to its template. Actually, that exposure is
automatically done.
You need to remember that ROR is full-stack, meaning it has a bunch of middleware it uses to create the most efficient responses for your users. This allows you to set local variables (session, params, flash are 3 I can think of)
To call from a view, you just need to reference this local variable, as below:
<%= flash[:key] %>
I've been developing on Rails for awhile and can't believe I haven't ran into this problem - maybe I am missing something simple?
My edit page displays information about the model being viewed. The model's to_s method returns the name attribute in this case, which is displayed in breadcrumbs and the page header.
I have a validation that the name cannot be blank and a simple update method:
def update
#model.update(permitted_params)
respond_with #model
end
The default ActionController responder will render my edit page automatically when #model is invalid, which it does. But #model still retains the invalid attributes so my breadcrumbs and page header are blank as they both display model.name which is "".
I could solve this by
def to_s
name.presence || name_was
end
But this application will be fairly large and most of my models will follow this same view pattern with the header containing a model attribute that could be invalid. I feel like using this pattern in to_s on all of my models will be frustrating to keep up with.
My current solution is to define this method in my custom responder, which reloads the #model if it is invalid:
class ApplicationResponder < ActionController::Responder
def initialize(*)
super
#resource.reload if has_errors?
end
end
This works but now any invalid request has an extra call to the database when the model is reloaded. Probably not a big deal, but still a code smell in my opinion.
Is there something I can do to stop Rails from keeping invalid attributes on #model after update? I am using Rails 4.1.0beta1 and Ruby 2.1 and have tried on Rails 4.0.0 as well.
Why not also provide some client-side validations to prevent users from submitting invalid details in the first place?
If you provided validations that give the user feedback before the page is re-rendered, you won't have the problem of ever having to work out how to display that invalid data (since it sounds like you are displaying data on the edit page itself).
I would recommend you check out Parsley.js or other similar JS based client-side validation.
The kind of behavior you are seeing, where they are still "stored" in the model object is a good thing in general because it allows you to automatically repopulate the input fields in a form with those incorrect values, so that a user can see what they entered wrong and change it.
If you are wanting to maintain certain 'correct' values statically on the page, like in a breadcrumb, I would copy those values off into their own variables and track them separately. For the breadcrumb on the application I work on, we actually store the link history in the browsers IndexedDB, and don't rely on what the current value of an object in the database is.
From my experience, most of the time the identifying attribute of an object, like it's name, isn't editable. If it is, I tend to make the name of my page something a little more general, like 'Edit Profile,' instead of 'Edit ', or 'Edit Organization' instead of 'Edit Name_of_Organization.'
If you really want to keep rendering the original name, I would just save that and track it through hidden HTML inputs. What you're doing with the to_s method isn't foolproof: if a user enters an invalid name that's not blank, you're going to render that invalid name.
I have a standard html form post that is being persisted and validated with a rails model on the server side.
For the sake of discussion, lets call this a "car" model.
When
car.save
is invoked the validators fire and set a list of fields in error within the model.
How best can I get an object back that has the fields whose validation failed removed, i.e. such that I don't have to delete the fields on the form the user entered correctly?
The validation patterns they use are great but how do I get a hook to when the validation fails such that I can delete the bogus entries from the model?
I could see manually iterating over the errors list and writing a big case statement but that seems like a hack.
In your controller, render the new action from your create action if validation fails, with an instance variable, #car populated from the user input (i.e., the params hash). Then, in your view, add a logic check (either an if block around the form or a ternary on the helpers, your choice) that automatically sets the value of the form fields to the params values passed in to #car if car exists. That way, the form will be blank on first visit and in theory only be populated on re-render in the case of error. In any case, they will not be populated unless #car is set.
EDIT:
Don't do this (see comments) but if you must here is how:
I ended up using a call to
#car.invalid?
inside of a loop to remove the invalid entries:
params[:car].each do | key, array|
if #car.errors.invalid?(key)
#car[key.to_sym] = ''
end
end
render :action=> 'new'
Note the use of the render here vs. redirect. Getting error messages to persist from the rails model validation across a redirect appears tricky, see:
Rails validation over redirect
I still feel like there should be a helper method for this or a better way.
I am currently using a link_to helper in View to pass parameters like title , author ,image_url and isbn back to controller
<%= link_to 'Sell this item',new_item_path(:title => title, :author => authors, :image_url=>image, :image_url_s=>image_s, :isbn=>isbn, :isbn13=>isbn13 ) %>
Controller will then assign the parameters to an object to be used by a form in View later(in new.html.erb)
def new
#item = Item.new
#item.title = params[:title]
#item.author = params[:author]
#item.image_url = params[:image_url]
#item.image_url_s = params[:image_url_s]
#item.isbn = params[:isbn]
#item.isbn13 = params[:isbn13]
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #item }
end
end
new.html.erb will then be called.
This is all working fine but the url shows all the parameters
http://localhost:3000/items/new?author=Michael+Harvey&image_url=http://ecx.images-amazon.com/images/I/51vt1uVjvLL._SL160_.jpg&image_url_s=http://ecx.images-amazon.com/images/I/51vt1uVjvLL._SL75_.jpg&isbn13=9780307272508&isbn=0307272508&title=The+Third+Rail
Is there any way I can make the parameters not show up on the URL?
Maybe you could encode the parameters and decode them in the controller to deter users who may want to modify the url? Might be overkill but...
>> author=ActiveSupport::Base64.encode64("author=jim")
=> "YXV0aG9yPWppbQ==\n"
>> ActiveSupport::Base64.decode64(author)
=> "author=jim"
A POST can be used to move the parameters out of the URL and into the request, but this is not the "correct" or best practice. HTTP standards are such that non-GET requests are meant to be used only for requests that change state on the server. This is why you get a warning when you refresh a page that was generated in response to a POST.
There is nothing wrong with having parameters in the URL. So much focus should not be made on what appears to the URL bar, let alone what's after the ?. If however you have some need (i.e. insistence of a client) to remove them, you have several options, two of which John mentions.
I'm assuming your "new" action is REST-style, in that it's generating a form that would have to be submitted to change state on the server. Therefore your options might be:
Use POST, even though it's not standard compliant. Not recommended.
Use AJAX GET. This requires javascript, and ajax handling does add requirements such as the use of a JS framework and testing.
Use GET (or POST), but capture the parameters and store them, the redirect the user back to another clean URL that displays those stored value. You could store those in the session hash, or create a database record of them. Actually you really should use POST in this case, since you are effectively changing state on the server by storing those parameters. In this case, if the user refreshes the page he is directed to, those parameters will be preserved. This effectively removes the browser warning on refresh, something I can certainly appreciate.
There are two options that I can see and both involve JavaScript:
Have the link populate hidden form fields for the parameters and then submit the form using an HTTP POST request
Have the link submit an AJAX request to the controller action (using an HTTP GET unless clicking the link changes server-side state, in which case a POST should be used)
I think I would go with the second approach.
Why not write them to the session? It looks like you might have less than 4k in data there. Just remember to wipe it.
As a long-time Ruby and Rails user, it never struck me until today to really think about the get-and-redirect pattern in Rails. The typical example of this would be calling a create() action, and then redirecting the user to a show() action to display the newly-created item:
class JournalEntries
def index
#entries = JournalEntry.all
end
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
redirect_to :action => "index"
end
end
However, this has the inherent disadvantage that you are doubling your network traffic. This both slows down your users' site experience, as well as increasing your bandwidth charges.
So why not just do this instead:
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
index
Same output, and no extra overhead required. But in addition to this, there is an even more substantial problem: redirect_to can only redirect using GET. This causes major problems for RESTful apps that use four different HTTP methods.
In my case, I wanted a user to be able to call /journals/8 and retrieve the Journal with that ID. If it wasn't found, I wanted to create a new, empty Journal object. In either case, the Journal object would then be sent to the caller.
Note that the create() method in RESTful Rails is routed from "POST /players". But since redirect_to (and the underlying HTTP redirect) can only send GET requests, it actually redirects to "GET /players", which is the index() method. This behavior is clearly wrong.
The only solution I could think of was to simply call create() instead of redirect_to() as in my example above. It seems to work fine.
Any thoughts on why redirect_to is preferred to calling actions directly?
If they do a page refresh they don't get that annoying "Resend data?" popup
It's not just that the popup is annoying (and makes no sense to most users) -- if the user clicks "yes, re-do the POST", he'll end up creating another Journal Entry (or whatever).
Also, it's annoying for the URL to read /posts/create instead of /posts since the user cannot copy / re-use it.
The reason for it is as you point out. You redirect to a GET request, which is correct when it comes to REST (only do updates with POST/PUT, only get data with GET).
A redirect surely gives a little overhead with the redirect, but since no data is actually being sent between the browser and the server except for the POST data and the redirect (which is only sending the new url to the browser) I don't think that the issue of bandwith is of concern.
But on another point, you should not redirect to /journals (by calling redirect_to :index), you should redirect it to the newly created journal entry (by calling redirect_to #entry) which will work if you set up the routes correctly by, for instance map.resources :journals
Update:
I think, for creating the Journal when one doesn't exist, you should ask the user for more input. What is the reason for you to create the entry? The entry should have some text or some other input from the user, so I think from a REST (and rails) perspective you should actually redirect it to the new() method (with a GET request) where the user can input the additional information, that one will then POST the input and create the entry and after redirect to the newly created entry.
If you don't have any extra information that needs to put in, I'm not sure how to do it in a RESTful way, but I would probably have done it by putting the creation logic in a separate method that I would call from the create() and the show() method and then just continue with the show(), not redirecting at all, but also not calling the resource method.
I'm a Python/Django person, but the reasons for the redirect is language agnostic:
If they do a page refresh they don't get that annoying "Resend data?" popup.
This gives you a completely clean, RESTful URL for the page they are looking at. If you used POST it might not matter that much, but if GET was used for the update then you definitely want to get rid of any dangling params.