Rails Best Practices for RESTful controller CREATE and UPDATE methods - ruby-on-rails

OK, I am trying to understand the best practices for the CREATE and UPDATE methods for both HTML and XML formats. The default code for a controller that the rails generator generates is a little unclear to me.
For the CREATE method, given a good save, the generator says to "redirect_to(#whatever)" for HTML and "render :xml => #whatever, :status => :created, :location => #whatever" for XML.
Given a bad save, the generator says to "render :action => 'new'" for HTML and "render :xml => #whatever.errors, :status => :unprocessable_entity" for XML.
However, for the UPDATE method, given a good update, the generator says to "redirect_to(#whatever)" for HTML and "head :ok" for XML.
And, given a bad update, the generator says to "render :action => 'edit'" for HTML and "render :xml => #whatever.errors, :status => :unprocessable_entity" for XML.
I understand this, and it makes sense to me, and works just fine - BUT, I have two questions:
First, for a successful CREATE and UPDATE, HTML format, why "redirect_to(#whatever)" instead of "render :action => 'show'"? I understand the differences between redirect and render, just more curious about which way you guys tend to do it and why. It seems the redirect would be an unnecessary extra trip for the browser to make.
Second, why "head :ok" upon successful UPDATE via XML, but "render :xml => #whatever, :status => :created, :location => #whatever" upon successful CREATE via XML? This seems inconsistent to me. Seems like a successful UPDATE via XML should be the same as a successful CREATE via XML. Seems like you would need the new/updated object returned so you could test it. How do you guys do it and why?

I'd already written this out when Sam C replied, but here it is anyway :-)
For the first part - why redirect instead of render? Two reasons I can think of:
1) It's consistent. If you rendered the show action and the user uses the back button later on return to that page, the user will see unexpected behaviour. Some versions of IE will give you some kind of session timeout error IIRC, other browsers may handle it a bit more gracefully.
Same goes for if the user bookmarked that page and returns to it at a later date using a GET request - they won't see the show action. Your app may throw an error or may render the index action because the user is requesting a URL like http://my.app.com/users, which would map to the index action when using a GET request.
2) if you render the show action without redirecting to a GET request and the user hits refresh, your browser will re-submit the POST request with all the same data, potentially creating duplicate instances of whatever it is you were creating. The browser will warn the user about this so they can abort, but it's potentially confusing and an unnecessary inconvenience.
As for the second part of your question, not too sure to be honest. My guess is that since you are already updating the object in question, you already have a copy of it so do not need another instance of it returned. Having said that, updating an object could trigger various callbacks which modify other attributes of the object, so returning that updated object with those modifications could make sense.

On create or update, redirect_to(#whatever) to clear the post, so that the user doesn't resubmit by refreshing. It also shows the correct url in the address bar for the create case, which posts to the collection path (/whatevers).
head :ok makes a minimal response on update, when usually you would already have the object in the dom. If you are updating the page after an update, the standard method is to use rjs views to update dom elements and render partials.

Related

Rails redirect or render to show errors

When a user makes an invalid create request for a resource, I want to send them back to the form and show them an error. As far as I can tell, calling render "new" is the standard way to do it.
This seems like a bad idea to me, because the create request has taken the user to /resources, whereas my "new" form is otherwise at /resources/new. If I use render "new", the URL in the address bar will not reflect what the user sees. If they make a GET request there, they'll end up on a different page.
I thought I could solve this problem by using redirect_to new_[resource]_path, but if I do that I lose access to the form data and errors. Surely this is not an uncommon problem. Is there a better way to deal with it?
I have the same problem with edit/update and other form/submit action pairs.
TL;DR:
class ResourcesController < ApplicationController
def new
#resource = Resource.new(resource_params)
if resource_params.present?
#resource.validate
end
end
def create
#resource = Resource.new(resource_params)
if #resource.save
redirect_to #resource, notice: 'Resource has been created'
else
redirect_to new_resource_url(resource: resource_params)
end
end
private
def resource_params
params.fetch(:resource, {}).permit(...)
end
end
I asked this myself as well in my early beginner days, on why Rails scaffold generator generates the def create action to render :new if saving failed, instead of redirecting to the correct URL just something like above, which would confuse the users because their URL would have changed from /resources/new into /resources even though they would still see the exact same Resource form on the page, and therefore they would not be able to reload the page or copy this /resources URL (for example, if they want to share this URL to someone else), because should they share this /resources URL, the others would see a list of Resources on the page instead of what the original user would have expected to see from the copied URL: which is the form page.
After the user submits the form, the URL they see on the address bar should have changed into POST http://localhost:3000/resources instead of just simply http://localhost:3000/resources. The browser hides the HTTP Method being used, that's why this leads to their possible confusion that /resources seems to have been both sometimes: a form page, sometimes a list of resources page. However, speaking of UX, every time anyone enters something or pastes something in the URL address bar of the browser, it is always automatically implied to be doing a GET request. Therefore, it makes sense for the browser-developers to just simply hide the HTTP method (i.e. GET in particular) from the users as to not confuse them.
From my answer above, I only used something like this once before (because there was a certain action that demanded me not to change the URL from the referrer form page). However, I normally render :new instead of redirect_to new_resources_url, simply because:
a redirect_to new_resource_url(resource_params) would take twice as much time and data-transmitted than simply rendering :new. Why? Because you redirect (opening up a new request) with the exact same parameters anyway. Just imagine if your form page is soooo big, and has so many input fields.
and also that there's a limit to how long a URL can be, of which won't guarantee to work if you have a very big form with very long text fields. See this SO
Updated:
As you have said though, why not just POST /resources/new instead of POST /resources when the form is submitted, right? This will solve the redirect_to new_resource_url(resource_params) problem I've shown above, because the URL after form-submit would have been the same and you can just simply render :new, then. And I actually agree on that, and I've used something like this also before long time ago. The main reason I don't use this is that it is not inline with REST standards. That is: POST /resources/new means that you're creating a Resource object "inside" the resources/new location, which then means by REST, after submitting the form, I would and should be able to access this newly created resource by doing something like GET /resources/new/the_newly_created_record, except that... you can't.
But you can still use POST /resources/new, although I would not recommend it on a normal Rails application like yours, and however strictly discourage it on an API-based Rails application.
You may be overthinking this. How about get /resources the index page vs post /resources the create action? Same url in the address bar!
It's not a problem. Neither a technical problem or an aesthetic problem.
render "new" means: render this template, not: go to this route. And although templates often have the name of the corresponding action, it's not a requirement.

Rails - any fancy ways to handle 404s?

I have a rails app I built for an old site I converted from another cms (in a non-rails language, hehe). Most of the old pages are mapped to the new pages using routes.rb. But there are still a few 404s.
I am a rails newb so I'm asking if there are any advanced ways to handle 404s. For example, if I was programming in my old language I'd do this:
Get the URL (script_name) that was being accessed and parse it.
Do a lookup in the database for any keywords, ids, etc found in the new URL.
If found, redirect to the page (or if multiple records are found, show them all on a results page and let user choose). With rails I'd probably want to do :status => :moved_permanently I'm guessing?
If not found, show a 404.
Are there any gems/plugins or tutorials you know of that would handle such a thing, if it's even possible. Or can you explain on a high level how that can be done? I don't need a full code sample, just a push in the right direction.
PS. It's a simple rails 3 app that uses a single Content model.
Put this in routes (after every other route that you have, this will capture every url)
match '*url' => 'errors#routing'
And now in errors controller in routing action you can implement any fancy logic that you want, and render a view as always (you might want to add :status => 404 to the render call). Requested url will be available in controller as params[:url].
There is an ugly way of doing this:
render :file => "#{RAILS_ROOT}/public/404.html", :layout => false, :status => 404
Maybe someone can come with a better solution.

Ruby on Rails: How to pass parameters from view to controller with link_to without parameters showing up in URL

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.

Is there any harm in using a typical GET action for a PUT? (RESTfully speaking)

I have an action that doesn't require a form. So it really only needs the one 'edit' method instead of the RESTful 'edit' --> 'update'. Is there any reason not to do this or a better way?
def edit
#Do a POST(PUT)
end
The harm is that a user could easily navigate to that url and perform a potentially destructive action.
/noform/edit #URL typed by user => Action Performed
/noform/update #URL typed by user => Error is thrown, No Action Performed
A normal browsing experience generates GET requests to the server. The assumption is, any page you can easily navigate to (or type into your address bar) will not perform any data changing functions.
A POST request, generated via a form submission or a AJAX request expects the result that data is changed on the server.
Similarly the two rails "faked" versions of PUT and DELETE also are not actions you could simply navigate to using a browser.
The solution
The solution is to have only the update action and where you originally would have linked to edit use something like the following:
button_to "Add new tracker", noform_path, :method => :put
If there is any type of error, you may still need an edit path to show the user so they can correct something. But from what you have described, a single update action should do the trick.
Gets should always be idempotent -- that is they should not perform any action that will alter the state of the application, database, etc.
Just as an aside -- in true RESTful form an edit would be performed by an HTTP Update action, but Rails simulates this with a post and a hidden value on the form, since browsers don't have HTTP Updates.
It's still not clear to me why you need an update without an input field. Perhaps a little more detail would be helpful.

redirect_to doesn't work well for RESTful apps?

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.

Resources